Библиотеки обработки ошибок в Rust

thiserrorУдобный производный макрос для пользовательских типов ошибок
snafuФреймворк для обработки и отчёта об ошибках с контекстом
anyhowГибкая библиотека для обработки и отчёта об ошибках

thiserror vs snafu

thiserror

thiserror — это лёгкая библиотека, предоставляющая производный макрос для упрощения определения ошибок.

Особенности:

  • Лаконичный синтаксис с минимальным boilerplate
  • Идеально подходит для создания библиотек типов ошибок и API
  • Чаще используется при создании библиотек для других разработчиков
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataError {
    #[error("Ошибка базы данных: {0}")]
    DatabaseError(#[from] sqlx::Error),
    
    #[error("Ошибка валидации: {0}")]
    ValidationError(String),
    
    #[error("Запись не найдена")]
    NotFound,
}

snafu

snafu предлагает более комплексный фреймворк обработки ошибок с акцентом на контекст и цепочки ошибок.

Особенности:

  • Поощряет точное добавление контекста через паттерн "селекторов контекста"
  • Рекомендует подход "одно перечисление ошибок на модуль"
  • Поддерживает варианты ошибок в виде структур и кортежей
  • Встроенная поддержка трассировки стека
use snafu::{Snafu, ResultExt, Backtrace};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Не удалось прочитать конфигурационный файл {filename:?}"))]
    ReadConfig {
        filename: String,
        source: std::io::Error,
        backtrace: Backtrace,
    },
    
    // Также поддерживается стиль кортежей
    #[snafu(display("Ошибка ввода-вывода"))]
    Io(#[snafu(source)] std::io::Error, #[snafu(backtrace)] Backtrace),
}

// Пример использования селектора контекста
fn read_config(path: &str) -> Result<Config, Error> {
    std::fs::read_to_string(path).context(ReadConfigSnafu { filename: path })?;
    // ...
}

Сравнение

Характеристикаthiserrorsnafu
Лаконичность синтаксисаБолее лаконичныйБолее многословный
Контекст ошибокБазовая поддержкаБогатые механизмы контекста
МасштабируемостьНебольшие и средние проектыСредние и крупные проекты
Строк кода~2 строки на ошибку~5 строк на ошибку
Организация ошибокОбычно единое перечислениеПоощряет перечисления на уровне модуля
Поддержка трассировкиНет встроенной поддержкиВстроенная поддержка

Рекомендации по выбору:

  • Выбирайте thiserror, если нужны простые и понятные типы ошибок, особенно в библиотеках
  • Выбирайте snafu, если требуется более структурированная обработка ошибок, особенно в крупных приложениях

anyhow

anyhow принципиально отличается от предыдущих библиотек, фокусируясь на приложениях, а не библиотеках.

Особенности:

  • Разработан для обработки ошибок в приложениях, а не библиотеках
  • Предоставляет динамический тип anyhow::Error, который может содержать любую ошибку, реализующую трейт Error
  • Упрощает обработку ошибок разных типов
  • Не требует определения пользовательских типов ошибок
use anyhow::{Context, Result};

fn main() -> Result<()> {
    let config = std::fs::read_to_string("config.json")
        .context("Не удалось прочитать конфигурационный файл")?;
        
    let app_config: AppConfig = serde_json::from_str(&config)
        .context("Недопустимый формат конфигурации")?;
        
    // Используем Result<T> как псевдоним для Result<T, anyhow::Error>
    Ok(())
}

anyhow vs thiserror/snafu:

  • anyhow ориентирован на быструю разработку приложений
  • thiserror/snafu фокусируются на создании точных иерархий типов ошибок
  • anyhow обычно используется в коде приложений
  • thiserror/snafu обычно применяются в библиотеках

На практике anyhow и thiserror часто используются вместе: библиотеки определяют точные типы ошибок через thiserror, а приложения обрабатывают разнородные ошибки через anyhow.