「这是我参与2022首次更文挑战的第 7 天,活动详情查看:2022首次更文挑战」。
Trait Objects
在我们着手实现源码之前,让我们仔细看看它的返回: Option<&(dyn Error + 'static)>。 dyn Error 是一个 Trait Obj。除了它实现了Error的trait外,我们对这个类型一无所知。
Trait Objects,和泛型一样,是Rust中实现多态性的一种方式:调用同一接口的不同实现。泛型在编译时决定类型(静态调度),而 Trait Objects 会有运行时成本(动态调度)。
但是为什么标准库要返回 Trait Objects ?
它为开发者提供了一种方法来访问当前错误的根本原因,同时保持其不透明性。它不会泄露任何关于底层类型的信息,同时你只能访问 Error trait 所暴露的方法:Debug/Display/source
下面我们给 StoreTokenError 实现 Error:
//! src/routes/subscriptions.rs
// [..]
impl std::error::Error for StoreTokenError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
// The compiler transparently casts `&sqlx::Error` into a `&dyn Error`
Some(&self.0)
}
}
当编写需要处理各种 Error 的代码时,source() 是很有用的:它提供了一种结构化的方式来浏览错误链,而不需要知道你正在处理的具体错误类型是什么。
Error::source
如果我们看一下我们的日志记录,StoreTokenError 和 sqlx::Error 之间的因果关系是有点隐式的。我们推断一个是另一个的原因,因为它是它的一部分。
...
INFO: [HTTP REQUEST - END]
exception.details= StoreTokenError(
Database(
PgDatabaseError {
severity: Error,
code: "42703",
message:
"column 'subscription_token' of relation
'subscription_tokens' does not exist",
...
}
)
)
exception.message=
"A database failure was encountered while
trying to store a subscription token.",
target=tracing_actix_web::root_span_builder,
http.status_code=500
下面我们去寻找更明确的打印信息:
//! src/routes/subscriptions.rs
// Notice that we have removed `#[derive(Debug)]`
pub struct StoreTokenError(sqlx::Error);
impl std::fmt::Debug for StoreTokenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}\nCaused by:\n\t{}", self, self.0)
}
}
现在的日志记录没有给人留下任何想象的空间:
...
INFO: [HTTP REQUEST - END]
exception.details=
"A database failure was encountered
while trying to store a subscription token.
Caused by:
error returned from database: column 'subscription_token'
of relation 'subscription_tokens' does not exist"
exception.message=
"A database failure was encountered while
trying to store a subscription token.",
target=tracing_actix_web::root_span_builder,
http.status_code=500
exception.details 更容易阅读,并且仍然传达了我们之前所有的相关信息。
使用 source(),我们可以为任何实现Error的类型提供类似的表示:
//! src/routes/subscriptions.rs
// [...]
fn error_chain_fmt(
e: &impl std::error::Error,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
writeln!(f, "{}\n", e)?;
let mut current = e.source();
while let Some(cause) = current {
writeln!(f, "Caused by:\n\t{}", cause)?;
current = cause.source();
}
Ok(())
}
它遍历了导致我们错误的整个错误链。接下来我们可以改变我们对 StoreTokenError 的 Debug 实现,fmt() 中使用它来打印我们的错误链。
//! src/routes/subscriptions.rs
// [...]
impl std::fmt::Debug for StoreTokenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
error_chain_fmt(self, f)
}
}
结果是相同的 —— 如果我们想要类似的Debug表示,我们可以在处理其他错误时重用 error_chain_fmt。