# Операторы let-else и пропагации ошибок в Rust

> Продвинутый контроль выполнения в Rust: пропагация ошибок и отсутствие значений с помощью оператора ?, безопасное извлечение с let-else и требования к расходящимся блокам.

- **URL:** https://eagle2000.space/rust/rust-flow-control/
- **Date:** 2026-08-02

- **Tags:** rust, dev

---## Введение

Написание выразительного кода требует лаконичных инструментов управления потоком выполнения. В Rust для борьбы с глубокой вложенностью кода (так называемым «callback hell» или «лестницей скобок») используются мощные операторы:
* **Оператор `?`** — для быстрой передачи ошибок и отсутствия значений наверх по стеку вызовов.
* **Конструкция `let-else`** — для безопасного развертывания опровержимых шаблонов с гарантированным расходящимся выходом в случае неудачи.

Давайте изучим работу этих операторов на интерактивных примерах кода:


### 💻 Операторы let-else и ?


#### Шаг 1

```rust
fn find_number(option: Option<i32>) -> Option<i32> {
    // Оператор ? возвращает значение внутри Some(x), либо делает досрочный возврат None
    let num = option?;
    Some(num * 2)
}

fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
    // Оператор ? возвращает i32, либо немедленно пробрасывает ошибку Err вверх
    let val = s.parse::<i32>()?;
    Ok(val * 2)
}

fn main() {
    let result_opt = find_number(Some(21));
    let none_opt = find_number(None);

    let result_res = parse_and_double("10");
    let err_res = parse_and_double("abc");

    println!("opt success: {:?}, opt none: {:?}", result_opt, none_opt);
    println!("res success: {:?}, res err: {:?}", result_res, err_res);
}
```

**Оператор ? для пропагации ошибок**
- Оператор `?` используется для быстрого извлечения значений из `Option` и `Result`.
- Для `Option`: если результат `Some(x)`, извлекает `x`; если `None`, делает досрочный возврат `None` из функции.
- Для `Result`: если результат `Ok(x)`, извлекает `x`; если `Err(e)`, делает досрочный возврат `Err(e.into())`.


#### Шаг 2

```rust
fn handle_event(event: Option<String>) {
    // let-else извлекает значение из Some, либо выполняет расходящийся блок else
    let Some(name) = event else {
        println!("Событие не содержит имени, завершаем функцию.");
        return; // Обязательный расходящийся выход
    };

    println!("Обработка события: {}", name);
}

fn main() {
    handle_event(Some("Click".to_string()));
    handle_event(None);

    // Пример let-else в цикле с continue
    for i in 0..5 {
        let opt = if i % 2 == 0 { Some(i) } else { None };
        
        let Some(val) = opt else {
            continue; // Пропускаем нечетные числа
        };
        println!("Четное число: {}", val);
    }
}
```

**Конструкция let-else (Diverging let)**
- `let-else` позволяет сопоставить опровержимый шаблон (например, `Some(x)`) и связать переменную в текущей области видимости.
- Если шаблон не совпал, выполняется блок `else`, который обязан быть **расходящимся** (diverging).
- Расходящийся блок `else` не имеет права просто вернуть управление; он обязан завершиться с помощью `return`, `break`, `continue` или `panic!()`.


---

## Оператор `?` (Error & Option Propagation)

Оператор `?` используется для быстрой пропагации (передачи наверх) ошибок или отсутствующих значений. Он разворачивает успешный результат, а в случае неудачи — осуществляет немедленный досрочный возврат из текущей функции.

### 1. Работа с типом `Option`

Если результатом выражения является `Some(r)`, оператор `?` извлекает и возвращает `r`. Если результат `None`, происходит досрочный выход из функции с возвратом `None`.

```rust
// Эквивалентные конструкции:
fn foo() -> Option<u32> {
    let value = bar()?; // Использование оператора ?
    Some(value)
}

// Что происходит под капотом:
let value = match bar() {
    Some(r) => r,
    None => return None,
};
```

### 2. Работа с типом `Result`

Если результатом является `Ok(r)`, возвращается `r`. Если `Err(err)`, происходит досрочный возврат из функции значения `Err(err.into())` (с автоматическим приведением типов ошибок через типаж `From/Into`).

```rust
// Эквивалентные конструкции:
fn foo() -> Result<u32, Error> {
    let value = bar()?; // Использование оператора ?
    Ok(value)
}

// Что происходит под капотом:
let value = match bar() {
    Ok(r) => r,
    Err(err) => return Err(err.into()),
};
```

---

## Конструкция `let - else` (Diverging `let`)

Синтаксис `let-else` позволяет связать переменную по шаблону в случае успеха, либо выполнить расходящийся (diverging) блок кода в случае неудачи.

* **Синтаксис:** `let pattern = expression else { ... };`. Обратите внимание на обязательное наличие точки с запятой `;` после закрывающей фигурной скобки.
* **Критическое правило блока `else`:** Блок `else` обязан быть **расходящимся** (diverging), то есть он **не имеет права** возвращать управление в текущую область видимости обычным путем. Он обязан завершаться операторами:
  * `return` (выход из функции)
  * `break` (выход из цикла)
  * `continue` (переход на следующую итерацию цикла)
  * `panic!()` (завершение потока с ошибкой)

### Различие шаблонов

Обычный `let` работает только с неопровержимыми (irrefutable) шаблонами, которые всегда совпадают (например, кортежи, ссылки или простые переменные). Для опровержимых (refutable) шаблонов, таких как `Some(x)`, необходим `let-else` или `if let`.

```rust
// Примеры неопровержимых шаблонов для классического let:
let a = 1 + 2;
let (a, b) = some_tuple;

// Использование let-else для опровержимых шаблонов:
let Some(x) = some_option else { return; };
let Some(x) = some_option else { break; };
let Some(x) = some_option else { continue; };
let Some(x) = some_option else { panic!(); };
```

### Практический пример обработки потока событий в цикле

Использование `let-else` значительно уменьшает уровень вложенности кода по сравнению с `match` или `if let`:

```rust
loop {
    // Если get_event() вернул Err, прерываем цикл
    let Ok(event) = get_event() else {
        break;
    };
    
    // Если event равен None, переходим к следующей итерации
    let Some(event) = event else {
        continue;
    };
    
    // Переменная event гарантированно развернута и валидна в этой точке
    handle_event(event);
}
```

---

## Проверь свои знания!

Пройдите короткий интерактивный тест, чтобы закрепить понимание let-else и ?.


### 📝  let-else и оператор ? в Rust


**1. Что происходит под капотом при использовании оператора `?` на типе `Result<T, E>` в случае возврата `Err(err)`?**

- [ ] Программа паникует и завершает работу
- [ ] Функция немедленно прекращает выполнение и возвращает `Err(err.into())` на уровень выше
- [ ] Ошибка просто игнорируется, и возвращается значение по умолчанию
- [ ] Возвращается значение `Ok(err)`

**2. Какое критическое требование предъявляется к блоку `else` в конструкции `let-else`?**

- [ ] Он обязан возвращать значение того же типа, что и при успешном сопоставлении
- [ ] Он должен быть пустым
- [ ] Он обязан быть расходящимся (diverging) — то есть не возвращать управление в текущую область видимости обычным путем
- [ ] Он должен состоять только из одного макроса panic!

**3. Какие ключевые слова управления потоком разрешены в конце блока `else` в `let-else` внутри тела цикла?**

- [ ] Только return
- [ ] Любые операторы, включая return, break, continue или вызов panic!()
- [ ] Никакие, блок должен возвращать unit-тип `()`
- [ ] Только break

**4. В чём основное преимущество `let-else` по сравнению с `if let` при успешном извлечении значения?**

- [ ] Связанные переменные становятся доступными во внешней области видимости, что уменьшает вложенность кода (избегается «лестница скобок»)
- [ ] Связанные переменные видны только внутри блока `else`
- [ ] let-else работает быстрее, так как не производит никаких проверок шаблона
- [ ] let-else можно использовать с неопровержимыми шаблонами

**5. Какая пунктуация обязательна в самом конце выражения `let pattern = expr else { ... }`?**

- [ ] Точка с запятой `;`
- [ ] Двоеточие `:`
- [ ] Запятая `,`
- [ ] Никаких знаков препинания не требуется

