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 是一个枚举类型,T
和 E
是泛型类型参数,程序成功运行时返回的值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)
将 source
和 Display
方法直接转发到底层错误,而不添加额外的消息。这适用于需要“任何其他”变体的枚举。
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 。