Введение#
Написание выразительного кода требует лаконичных инструментов управления потоком выполнения. В Rust для борьбы с глубокой вложенностью кода (так называемым «callback hell» или «лестницей скобок») используются мощные операторы:
- Оператор
?— для быстрой передачи ошибок и отсутствия значений наверх по стеку вызовов. - Конструкция
let-else— для безопасного развертывания опровержимых шаблонов с гарантированным расходящимся выходом в случае неудачи.
Давайте изучим работу этих операторов на интерактивных примерах кода:
Оператор ? (Error & Option Propagation)#
Оператор ? используется для быстрой пропагации (передачи наверх) ошибок или отсутствующих значений. Он разворачивает успешный результат, а в случае неудачи — осуществляет немедленный досрочный возврат из текущей функции.
1. Работа с типом Option#
Если результатом выражения является Some(r), оператор ? извлекает и возвращает r. Если результат None, происходит досрочный выход из функции с возвратом None.
// Эквивалентные конструкции:
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).
// Эквивалентные конструкции:
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.
// Примеры неопровержимых шаблонов для классического 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:
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 и оператору пропагации ошибок ?.


