greathongtu 的 Blog

Rust Error Handling

从第一性原理推导Rust Error:

One API error, three stakeholders: Users, Programmers, machines.

model errors

first step

enum Falliable<T> {
  Ok(T),
  Err
}

second step

improve error reporting capabilities

enum Falliable<T> {
  Ok(T),
  Err(String),
}

third step

provide different error messages to users and programmers

enum Falliable<T> {
  Ok(T),
  Err {
    user_report: String,
    programmer_report: String,
  }
}

forth step

provide a stable contract to enable control flow

增加泛型参数Error

enum Falliable<T, Error> {
  Ok(T),
  Err {
    user_report: String,
    programmer_report: String,
    error: Error,
  }
}

for example:

enum CreateArticleError {
  RateLimited,
  InvalidInputs {},
  GenericError,
}

fn create_article() -> Falliable<T,CreateArticleError>
// inside function we can match the result to enable control flow

fifth step

avoid duplication

Both reports are generated even if might never used.

enum Falliable<T, Error> 
where
  Error: Debug + Display
{
  Ok(T),
  Err(Error)
}

因为泛型参数Error实现了Debug(用来给programmer看)和Display(用来给User看)

sixth step

in a layered application:

fn create_article() -> Falliable<ArticleId, CreateArticleError> 会调用send_http_request() -> Falliable<HttpResponse, HttpError>

这时在create_article()里我们会丢失错误的context

trait Error: Debug + Display {}
enum CreateArticleError {
  RateLimited,
  InvalidInputs {},
  GenericError{
    source: Box<dyn Error>
  },
}

final step

make it recursive

trait Error: Debug + Display {
  fn source(&self) -> Option<&(dyn Error + 'static)>
}

some patterns:

impl std::error::Error for all error types: derive Debug, impl Display and fn source().

Tip: Use thiserror to do that:

#[derive(thiserror::Error, Debug)]
pub enum CreateArticleError {
    #[error("failed due to rate limiting")]
    RateLimited,
    #[error("failed: {0}")]
    InvalidInputs {},
    #[error("something wrong")]
    GenericError(#[source] HTTPError)
}

best practise:

use my_module::Error;

use my_module::Result;

  • 测试场景:
pub type Result<T> = core::result::Result<T, Error>;
pub type Error = Box<dyn std::error::Error>;
  • production 场景: 再细分,写error enum