thiserror库提供了方便的派生宏,简化了Rust中自定义错误的创建和处理。

编程时,错误处理是无法规避且重要的地方,和Golang一样,Rust没有异常,只有错误。在使用Rust时,编译器会不断督促我们,处理哪些错误。这也使得程序 更加健壮,因为这使得代码运行部署前,就能发现错误并进行适当处理,基本不存在漏掉不处理的情况。

可恢复的(recoverable)和 不可恢复的(unrecoverable)错误

Rust 将错误分为两大类:可恢复的(recoverable)和 不可恢复的(unrecoverable)错误。使用Result<T,E>类型,处理可恢复的错误,panic!宏,在程序遇到 不可恢复的错误时直接中断程序的执行。

Error

Rust 使用Result类型类处理可恢复的错误。

1
2
3
4
enum Result<T, E>
    Ok(T),
    Err(E),
}

Result 是一个枚举类型,TE是泛型类型参数,程序成功运行时返回的值T, 程序运行失败时返回的错误类型E。

Rust在标准库中提供了一个trait,sdt::error::Error,目前错误处理都是基于这个trait来进行,一个结构体/枚举如果实现了这个trait,那么我们认为,它就是一个错误类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/// Errors may provide cause information. [`Error::source()`] is generally
/// used when errors cross "abstraction boundaries". If one module must report
/// an error that is caused by an error from a lower-level module, it can allow
/// accessing that error via [`Error::source()`]. This makes it possible for the
/// high-level module to provide its own errors while also revealing some of the
/// implementation for debugging.
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "Error")]
#[rustc_has_incoherent_inherent_impls]
#[allow(multiple_supertrait_upcastable)]
pub trait Error: Debug + Display {
    #[stable(feature = "error_source", since = "1.30.0")]
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        None
    }
    ...
}

使用thiserror宏,可以使用极大简化,Cargo.toml中添加:

1
2
[dependencies]
thiserror = "1.0"

编译器支持:要求rustc 1.56+。

thiserror 创建自定义错误

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("data store disconnected")]
    Disconnect(#[from] io::Error),
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
    #[error("invalid header (expected {expected:?}, found {found:?})")]
    InvalidHeader {
        expected: String,
        found: String,
    },
    #[error("unknown data store error")]
    Unknown,
}

Display

只要为struct或者每个成员提供#error("..."),那么就会自定义的错误生成Display实现,错误消息支持从错误中插入字段简写:

  • #[error(“{var}”)] ⟶ write!(“{}”, self.var)
  • #[error(“{0}”)] ⟶ write!(“{}”, self.0)
  • #[error(“{var:?}“)] ⟶ write!(”{:?}“, self.var)
  • #[error(“{0:?}“)] ⟶ write!(”{:?}“, self.0)

From

对于每一个含有#[from]属性的,都会生成一个From实现。

1
2
3
4
5
6
7
8
#[derive(Error, Debug)]
pub enum MyError {
    Io {
        #[from]
        source: io::Error,
        backtrace: Backtrace,
    },
}

嵌套错误

错误可以使用 error(transparent)sourceDisplay 方法直接转发到底层错误,而不添加额外的消息。这适用于需要“任何其他”变体的枚举。

1
2
3
4
5
6
7
8
9
use log::SetLoggerError;

#[derive(Debug, thiserror::Error)]
pub enum AppError {
    #[error("{0} not found")]
    NotFoundError(String),
    #[error(transparent)]
    RedisError(#[from] redis::RedisError),
}

#[from]属性标记意味着redis::RedisError会被AppError中的RedisError转换为AppError::RedisError

thiserror vs anyhow

thiserror 库作者建议到,如果你是在开发库,涉及自己的专用错误类型,便于调用者发生错误时准确收到信息,则用thiserror ,而如果是开发应用,不关心返回的是什么错误类型,只是希望处理起来更简单则使用 anyhow 。