# Обработка ошибок в Rust (Result, ?)

> В Rust восстановимые ошибки выражаются типом Result<T, E>, отсутствие значения — Option<T>, а оператор ? лаконично пробрасывает ошибку вызывающему коду; неустранимые сбои вызывают panic!.

Источник: https://llmdocs.ru/wiki/rust-errors/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Восстановимые ошибки**: Result<T, E> — Ok(T) или Err(E)
- **Отсутствие значения**: Option<T> — Some(T) или None
- **Проброс ошибок**: оператор ? (Rust 1.13, 2016; ранее макрос try!)
- **Неустранимые ошибки**: panic!
- **Трейт ошибок**: std::error::Error
- **Популярные крейты**: thiserror (библиотеки), anyhow (приложения)
**Обработка ошибок в Rust** основана на типах. Операция, которая может завершиться неудачей, возвращает **`Result<T, E>`** — перечисление из двух вариантов: `Ok(значение)` при успехе или `Err(ошибка)` при неудаче. Значение, которого может не быть, описывается **`Option<T>`** — `Some(значение)` или `None`. Компилятор не позволит использовать результат, не разобрав оба варианта, а **оператор `?`** позволяет коротко передать ошибку наверх. Для действительно неустранимых ситуаций есть **`panic!`**.[1]

В Rust нет исключений и нет `null`: всё, что может пойти не так, видно в сигнатуре функции.

## Result и Option

```rust
enum Result<T, E> { Ok(T), Err(E) }
enum Option<T>    { Some(T), None }
```

```rust
use std::fs;

fn read_port(path: &str) -> Result<u16, String> {
    let text = match fs::read_to_string(path) {
        Ok(t) => t,
        Err(e) => return Err(format!("не удалось прочитать {path}: {e}")),
    };
    text.trim().parse::<u16>().map_err(|e| format!("некорректный порт: {e}"))
}

match read_port("port.txt") {
    Ok(port) => println!("слушаем порт {port}"),
    Err(msg) => eprintln!("ошибка: {msg}"),
}
```

У обоих типов десятки удобных методов-комбинаторов: `map`, `map_err`, `and_then`, `unwrap_or`, `unwrap_or_else`, `ok_or`, `ok()`. Они позволяют преобразовывать значения и ошибки без вложенных `match`.[2]

## Оператор ?

Оператор `?` после выражения типа `Result` делает следующее: при `Ok` извлекает значение, при `Err` немедленно возвращает ошибку из текущей функции, предварительно преобразовав её в нужный тип через трейт `From`. Он появился в Rust 1.13 (2016), заменив макрос `try!`.[4]

```rust
use std::{fs, io, num::ParseIntError};

#[derive(Debug)]
enum ConfigError {
    Io(io::Error),
    Parse(ParseIntError),
}
impl From<io::Error> for ConfigError { fn from(e: io::Error) -> Self { ConfigError::Io(e) } }
impl From<ParseIntError> for ConfigError { fn from(e: ParseIntError) -> Self { ConfigError::Parse(e) } }

fn read_port(path: &str) -> Result<u16, ConfigError> {
    let text = fs::read_to_string(path)?;        // io::Error → ConfigError::Io
    let port = text.trim().parse::<u16>()?;      // ParseIntError → ConfigError::Parse
    Ok(port)
}
```

Тот же оператор работает с `Option` в функциях, возвращающих `Option`, и в функции `main`, если она возвращает `Result`.

## panic!

`panic!` прерывает поток выполнения при ошибке программиста или нарушенном инварианте: выход за границы вектора, деление на ноль в целых числах, явный `unreachable!()`. Методы `unwrap()` и `expect("сообщение")` извлекают значение из `Result` или `Option`, вызывая панику при ошибке. Они уместны в прототипах, тестах и там, где ошибка действительно невозможна; в рабочем коде предпочтительнее обработка или `expect` с объяснением, почему значение обязано быть.

## Трейт Error и экосистема

Типы ошибок принято реализовывать с трейтом `std::error::Error` (требует `Debug` и `Display`) и методом `source()`, который даёт доступ к исходной причине.[3] Писать такие реализации вручную утомительно, поэтому сложились два популярных крейта Дэвида Толнея:

| Крейт | Для чего | Подход |
|---|---|---|
| **thiserror** | библиотеки | собственное перечисление ошибок; `derive` генерирует `Display`, `Error` и `From`[5] |
| **anyhow** | приложения | единый тип `anyhow::Error` для любых ошибок, удобное добавление контекста[6] |

```rust
// библиотека: точные, различимые ошибки
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
    #[error("запись {0} не найдена")]
    NotFound(u64),
    #[error("ошибка базы данных")]
    Db(#[from] sqlx::Error),
}

// приложение: ошибки с контекстом
use anyhow::{Context, Result};

fn load() -> Result<Config> {
    let raw = std::fs::read_to_string("config.toml").context("чтение config.toml")?;
    let cfg: Config = toml::from_str(&raw).context("разбор config.toml")?;
    Ok(cfg)
}
```

Правило большого пальца: библиотека должна давать вызывающему коду возможность различать ошибки (thiserror), а приложению чаще важно понятное сообщение с цепочкой причин (anyhow).

## Сравнение с Go и исключениями

| | Rust | [Go](https://llmdocs.ru/wiki/go-errors.md) | Исключения (Python, Java) |
|---|---|---|---|
| Где видна ошибка | в типе возврата | в типе возврата | не видна в сигнатуре (кроме checked exceptions) |
| Можно забыть обработать | нет — компилятор предупредит | да, если проигнорировать `err` | да |
| Проброс | `?` | `if err != nil { return err }` | автоматический |
| Стоимость | нулевая, обычное значение | обычное значение | раскрутка стека |

## Советы

- Не злоупотребляйте `unwrap()` — каждая паника в продакшене начинается с одного из них; линтер Clippy может запрещать их в рабочем коде (см. [Clippy и rustfmt](https://llmdocs.ru/wiki/clippy-rustfmt.md)).
- Добавляйте контекст при пробросе: что делала функция и с какими данными.
- Не превращайте все ошибки в строки — типизированные ошибки позволяют вызывающему коду реагировать по-разному.

## См. также

- [Rust (язык)](https://llmdocs.ru/wiki/rust.md)
- [Трейты (Rust)](https://llmdocs.ru/wiki/rust-traits.md)
- [Обработка ошибок в Go](https://llmdocs.ru/wiki/go-errors.md)
- [Владение и заимствование (Rust)](https://llmdocs.ru/wiki/ownership-borrowing.md)
- [Cargo и crates.io](https://llmdocs.ru/wiki/cargo.md)

## Источники

1. [The Rust Programming Language — Error Handling](https://doc.rust-lang.org/book/ch09-00-error-handling.html)
2. [Rust std — Module std::result](https://doc.rust-lang.org/std/result/)
3. [Rust std — Trait std::error::Error](https://doc.rust-lang.org/std/error/trait.Error.html)
4. [Rust Blog — Announcing Rust 1.13](https://blog.rust-lang.org/2016/11/10/Rust-1.13.html)
5. [thiserror — docs.rs](https://docs.rs/thiserror)
6. [anyhow — docs.rs](https://docs.rs/anyhow)
