Thiserror Error Handler

Суть

Создаём свой тип ошибки как enum, вешаем на него:

#[derive(thiserror::Error)]

Далее, описываем каждый вариант ошибки:

use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyLibError {
    #[error("Network failed: {0}")]
    Network(String),
    #[error("Invalid user ID: {id}")]
    InvalidUserId { id: u32 },
    #[error("File not found")]
    NotFound,
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
}

#[from] в Io означает ? превращает std::io::Error в MyLibError::Io(...).

Все функции библиотеки возвращают Result с ошибкой в MyLibError:

pub fn get_user(id: u32) -> Result<String, MyLibError> {
    if id == 0 {
        return Err(MyLibError::InvalidUserId { id });
    }
    std::fs::read_to_string("config.txt")?; // автоконвертирует тип ошибки
    Ok(format!("User {}", id))
}

Особенность применения:

  • Для работы с ошибками в БИБЛИОТЕКАХ (libs)!
  • В приложениях проще унифицировать тип ошибки, поэтому там применяется anyhow.

Пользователи библиотеки могут проверять по конкретным ошибкам:

use your_lib::{get_user, MyLibError};

match get_user(0) {
    Ok(name) => println!("Got {name}"),
    Err(MyLibError::InvalidUserId { id }) => {
        println!("You gave ID {id}, but that's invalid!");
        // обработать конкретно
    }
    Err(MyLibError::NotFound) => {
        println!("File missing, creating new one...");
        // создать файл
    }
    Err(e) => {
        println!("Other error: {e}");
    }
}

В случае с anyhow,  пользователи получили бы единый тип anyhow::Error и вынуждены были бы парсить строки (плохой код).

Пример применения:

// Библиотека (image_loader.rs)
#[derive(thiserror::Error, Debug)]
pub enum ImageError {
    #[error("File too large: {size} bytes")]
    TooLarge { size: u64 },
    #[error("Unsupported format: {format}")]
    BadFormat { format: String },
    #[error("Corrupted data")]
    Corrupted,
}

pub fn load_image(path: &str) -> Result<Vec<u8>, ImageError> {
    // ...
}

// Кто-то используем библиотеку:
match image_loader::load_image("photo.png") {
    Ok(data) => render(data),
    Err(ImageError::TooLarge { size }) => 
        println!("Your image is {size} bytes, max is 10MB"),
    Err(ImageError::BadFormat { format }) => 
        println!("Sorry, we don't support {format} files"),
    Err(ImageError::Corrupted) => 
        println!("The image is broken"),
}