thiserror 与 anyhow 常被放在一起比较,其实它们解决的是两件事。这篇真的下载编译这两个 crate,展开 thiserror 的宏看它生成了什么,再用 anyhow 的链式上下文,最后用一个例子说明两者的边界。
运行结果(退出码 0):
12 -> Ok(12)
missing -> Display: 键 missing 不存在 source: None Debug: NotFound("missing")
io -> Display: 读取 /不存在 失败 source: Some("entity not found") Debug: Io { path: "/不存在", source: Kind(NotFound) }
abc -> Display: 无效数字 source: Some("invalid digit found in string") Debug: Parse(ParseIntError { kind: InvalidDigit })
size_of::<StoreError>() = 32手写版本见自定义错误类型。thiserror 从 crates.io 下载编译(2.0.21),需要联网。
展开后与 StoreError 相关的 impl(共 63 行,下面显示前 70 行):
impl ::core::fmt::Debug for StoreError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
StoreError::NotFound(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"NotFound", &__self_0),
StoreError::Io { path: __self_0, source: __self_1 } =>
::core::fmt::Formatter::debug_struct_field2_finish(f, "Io",
"path", __self_0, "source", &__self_1),
StoreError::Parse(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Parse",
&__self_0),
}
}
}
impl ::thiserror::__private21::Error for StoreError {
fn source(&self)
->
::core::option::Option<&(dyn ::thiserror::__private21::Error +
'static)> {
use ::thiserror::__private21::AsDynError as _;
#[allow(deprecated)]
match self {
StoreError::NotFound { .. } => ::core::option::Option::None,
StoreError::Io { source: source, .. } =>
::core::option::Option::Some(source.as_dyn_error()),
StoreError::Parse { 0: source, .. } =>
::core::option::Option::Some(source.as_dyn_error()),
}
}
}
impl ::core::fmt::Display for StoreError {
fn fmt(&self, __formatter: &mut ::core::fmt::Formatter)
-> ::core::fmt::Result {
use ::thiserror::__private21::AsDisplay as _;
#[allow(unused_variables, deprecated, clippy ::
used_underscore_binding)]
match self {
StoreError::NotFound(_0) =>
match (_0.as_display(),) {
(__display0,) =>
__formatter.write_fmt(format_args!("键 {0} 不存在",
__display0)),
},
StoreError::Io { path, source } =>
match (path.as_display(),) {
(__display_path,) =>
__formatter.write_fmt(format_args!("读取 {0} 失败",
__display_path)),
},
StoreError::Parse(_0) => __formatter.write_str("无效数字"),
}
}
}
impl ::core::convert::From<std::num::ParseIntError> for StoreError {
#[allow(clippy :: redundant_field_names)]
fn from(source: std::num::ParseIntError) -> Self {
StoreError::Parse { 0: source }
}
} 运行结果(退出码 0):
监听 8080
--- 输入 ""
{} : 启动服务失败
{:#} : 启动服务失败: 端口为空
{:?} :
启动服务失败
Caused by:
端口为空
原因链: ["启动服务失败", "端口为空"]
--- 输入 "80"
{} : 启动服务失败
{:#} : 启动服务失败: 端口 80 是保留端口
{:?} :
启动服务失败
Caused by:
端口 80 是保留端口
原因链: ["启动服务失败", "端口 80 是保留端口"]
--- 输入 "abc"
{} : 启动服务失败
{:#} : 启动服务失败: 解析端口 "abc" 失败: invalid digit found in string
{:?} :
启动服务失败
Caused by:
0: 解析端口 "abc" 失败
1: invalid digit found in string
原因链: ["启动服务失败", "解析端口 \"abc\" 失败", "invalid digit found in string"]
size_of::<anyhow::Error>() = 8 size_of::<Result<(), anyhow::Error>>() = 8
downcast_ref::<ParseIntError>: Some("invalid digit found in string") 运行结果(退出码 0):
typed:调用方 match 到 Timeout,可以按 5 秒重试
anyhow:调用方拿到的是 超时 5 秒
只能 downcast_ref 才能还原:Timeout(5)? 的展开见? 运算符展开过程。