Что такое Result и Option в Rust и как с ними работать?

В Rust Result и Option — это два базовых перечисления (enums), с помощью которых язык реализует безопасную обработку ошибок и работу с отсутствующими значениями. Вместо привычных null, None, undefined, try/catch или исключений, Rust принудительно заставляет программиста обрабатывать каждую потенциальную ошибку или неопределённость.

Option<T>: значение либо есть, либо нет

Option<T> используется, когда значение может отсутствовать. Он определяется так:

enum Option&lt;T&gt; {
Some(T),
None,
}
  • Some(T) — обёртка над существующим значением.

  • None — означает отсутствие значения.

Пример:

fn get_first_element(vec: &Vec&lt;i32&gt;) -> Option&lt;i32&gt; {
vec.get(0).copied()
}
fn main() {
let numbers = vec!\[10, 20, 30\];
match get_first_element(&numbers) {
Some(n) => println!("Первый элемент: {}", n),
None => println!("Вектор пустой"),
}
}

Метод .get(index) у Vec возвращает Option<&T>, а .copied() делает из &T — T.

Почему это важно:

  • Rust не допускает null-ссылок.

  • Ошибка из-за null dereference — невозможна, если код компилируется.

  • Компилятор заставляет вас обработать None.

Удобные методы для работы с Option:

  • .is_some(), .is_none()

  • .unwrap(), .expect(msg)

  • .map(), .and_then(), .unwrap_or(), .unwrap_or_else()

  • if let Some(val) = ... и match

Result<T, E>: успешное значение или ошибка

Result<T, E> применяется для обработки ошибок. Он определён так:

enum Result&lt;T, E&gt; {
Ok(T),
Err(E),
}
  • Ok(T) — успешный результат.

  • Err(E) — ошибка.

Пример:

fn divide(a: f64, b: f64) -> Result&lt;f64, String&gt; {
if b == 0.0 {
Err("Деление на ноль".to_string())
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("Результат: {}", result),
Err(e) => println!("Ошибка: {}", e),
}
}

Если использовать .unwrap() вместо match, то Err вызовет паник.

Преимущества Result:

  • В отличие от исключений, ошибки явные.

  • Типовая система обязывает обрабатывать Err.

  • Можно определить любой тип ошибки (String, io::Error, своё перечисление и т. д.)

Работа с Result и Option: идиомы Rust

1. ? оператор

Это синтаксический сахар, позволяющий пробрасывать ошибки вверх по стеку вызовов:

fn parse_number(input: &str) -> Result&lt;i32, std::num::ParseIntError&gt; {
let num = input.parse::&lt;i32&gt;()?;
Ok(num \* 2)
}

Если parse() вернёт Err, вся функция parse_number немедленно вернёт эту ошибку.

? работает как с Result, так и с Option:

fn get_first_char(s: &str) -> Option&lt;char&gt; {
let first = s.chars().next()?;
Some(first.to_ascii_uppercase())
}

Если .next() вернёт None, функция get_first_char вернёт None.

2. Комбинирование и трансформация

Вы можете использовать .map() и .and_then() для цепочек:

let maybe_len = Some("hello")
.map(|s| s.len());
let maybe_parsed = Some("42")
.and_then(|s| s.parse::&lt;i32&gt;().ok());

Для Result аналогично:

let parsed = "42"
.parse::&lt;i32&gt;()
.map(|n| n \* 2)
.unwrap_or(0);

Преобразование между Option и Result

Иногда нужно перейти от одного типа к другому:

let opt = Some(42);
let res: Result&lt;i32, &str&gt; = opt.ok_or("Нет значения");
let res2: Result&lt;i32, String&gt; = "42".parse().map_err(|e| e.to_string());

Методы:

  • .ok_or(err) превращает Option в Result

  • .ok_or_else(|| ...) — ленивый аналог

  • .ok() превращает Result в Option, теряя ошибку

Практические случаи использования

  • Option — когда отсутствие значения нормально и ожидаемо (например, элемент в коллекции, парсинг строки, поиск по ключу).

  • Result — когда может произойти ошибка, и вы хотите либо обработать её, либо пробросить дальше (например, работа с файлами, сетевыми запросами, преобразования типов).

Почему это важно в Rust

Главная идея в Rust: непрерывный контроль над ситуацией и предсказуемость исполнения программы. Option и Result обеспечивают:

  • Безопасность на уровне компиляции.

  • Гибкие способы обработки и пробрасывания ошибок.

  • Минимум runtime-накладных расходов (всё разворачивается в enum-ветвления, без try/catch).

Вместо ловушек вроде null, throw, panic, вы работаете с кодом, где всё возможное поведение явно указано в типах, и компилятор помогает вам написать корректную программу.