Перейти к основному содержимому

5.13. Основные принципы обработки ошибок в Rust

Разработчику Архитектору

Основные принципы обработки ошибок в Rust

Основные принципы обработки ошибок в Rust

  1. Ошибки — это значения.
    Функции, которые могут завершиться неудачей, возвращают тип Result<T, E>, где:

    • T — тип успешного результата,
    • E — тип ошибки.
  2. Нет try/catch, throw, raise.
    Обработка ошибок происходит явно через сопоставление с образцом (match), макросы (?), или комбинаторы (map, and_then и т.д.).

  3. Паника (panic!) существует, но предназначена только для необратимых ошибок (например, нарушение инвариантов, ошибки в логике программы).

    • Вызывает разворачивание стека (unwinding) или немедленное завершение (abort).
    • Не должна использоваться для обработки ожидаемых ошибок (например, отсутствие файла, недопустимый ввод).

Типы ошибок в Rust

Rust не определяет глобальную иерархию ошибок. Вместо этого используются конкретные типы, реализующие трейт std::error::Error.

1. Стандартные типы ошибок из std

  • std::io::Error — ошибки ввода-вывода:

    • Возникают при работе с файлами, сетью, процессами.
    • Содержит код ошибки (std::io::ErrorKind), например:
      • NotFound
      • PermissionDenied
      • ConnectionRefused
      • InvalidInput
      • TimedOut
      • и др.
  • std::fmt::Error — ошибка при форматировании (например, в пользовательском Display::fmt).

  • std::num::ParseIntError, std::num::ParseFloatError — ошибки при парсинге чисел.

  • std::str::Utf8Error — ошибка при проверке UTF-8.

  • std::string::FromUtf8Error — ошибка при создании String из байтов.

  • std::env::VarError — ошибка при чтении переменной окружения:

    • NotPresent
    • NotUnicode
  • std::ffi::FromBytesWithNulError, std::ffi::NulError — ошибки при работе с C-совместимыми строками.

  • std::path::StripPrefixError — ошибка при удалении префикса пути.

2. Трейт std::error::Error

Любой пользовательский тип ошибки должен реализовывать этот трейт (обычно автоматически через thiserror или вручную). Он требует:

  • Display::fmt — человекочитаемое сообщение,
  • Debug::fmt — техническое представление,
  • опционально: source() — ссылка на вложенную ошибку (цепочка причин).

3. Паники (не ошибки в обычном смысле)

Следующие ситуации вызывают panic!:

  • Выход за границы массива: index out of bounds
  • Деление на ноль
  • Нарушение assert! или unwrap() на Err/None
  • Некорректная работа с unsafe (например, двойное освобождение памяти)

Эти события не являются типами ошибок, а приводят к аварийному завершению потока.


Экосистемные практики

  • Библиотеки определяют собственные типы ошибок, часто с помощью крейта thiserror (для удобного определения) или anyhow (для упрощённой обработки в приложениях).
  • Цепочки ошибок строятся через поле source, а не через наследование.
  • Нет общего корневого типа ошибок, кроме как через динамическую диспетчеризацию (Box<dyn std::error::Error>).

Примеры

use std::fs;
use std::num::ParseIntError;

fn read_number_from_file(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
let contents = fs::read_to_string(path)?; // io::Error
let number: i32 = contents.trim().parse()?; // ParseIntError
Ok(number)
}

Здесь возможны две конкретные ошибки: std::io::Error и ParseIntError. Обе реализуют std::error::Error и могут быть упакованы в Box<dyn Error>.


Нет типов ошибок

В Rust нет единого списка «типов ошибок», аналогичного другим языкам, потому что:

  • Ошибки представлены конкретными типами, а не иерархией классов.
  • Нет встроенных исключений вроде IndexError, KeyError.
  • Стандартная библиотека предоставляет набор специализированных типов ошибок для разных подсистем.
  • Пользовательские ошибки определяются явно и композиционно.

Таким образом, вместо таксономии исключений Rust предлагает типобезопасную, композиционную модель обработки ошибок через Result и трейт Error.