「这是我参与2022首次更文挑战的第 10 天,活动详情查看:2022首次更文挑战」。
用 thiserror 替代模板
前面我们花了大约90行代码来实现 SubscriberError 和围绕它的所有机制,以实现所需的行为并在我们的日志中获得有用的诊断。
这是大量的代码,还有大量的模板(例如,源代码或From实现)。我们能做得更好吗?
我不确定我们能不能少写一些代码,但我们可以找到一个不同的方法:我们可以用一个宏来生成所有的模板。
碰巧的是,在生态系统中已经有一个很好的 crate 来实现这个目的:thiserror。让我们把它添加到我们的依赖项中。
#! Cargo.toml
[dependencies]
# [...]
thiserror = "1"
它提供了一个派生宏来生成我们刚刚手工编写的大部分代码。让我们看看它的实际应用:
//! src/routes/subscriptions.rs
// [...]
#[derive(thiserror::Error)]
pub enum SubscribeError {
#[error("{0}")]
ValidationError(String),
#[error("Failed to acquire a Postgres connection from the pool")]
PoolError(#[source] sqlx::Error),
#[error("Failed to insert new subscriber in the database.")]
InsertSubscriberError(#[source] sqlx::Error),
#[error("Failed to store the confirmation token for a new subscriber.")]
StoreTokenError(#[from] StoreTokenError),
#[error("Failed to commit SQL transaction to store a new subscriber.")]
TransactionCommitError(#[source] sqlx::Error),
#[error("Failed to send a confirmation email.")]
SendEmailError(#[from] reqwest::Error),
}
// We are still using a bespoke implementation of `Debug`
// to get a nice report using the error source chain
impl std::fmt::Debug for SubscribeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
error_chain_fmt(self, f)
}
}
pub async fn subscribe(/* */) -> Result<HttpResponse, SubscribeError> {
// We no longer have `#[from]` for `ValidationError`, so we need to
// map the error explicitly
let new_subscriber = form.0.try_into().map_err(SubscribeError::ValidationError)?;
// [...]
}
我们把它削减到了21行--还不错!让我们来分析一下上面正在发生什么。
thiserror::Error 是一个通过 #[derive(/* */)] 属性使用的程序性宏。
我们以前也见过并使用过这些。例如:
#[derive(Debug)]#[derive(serde::Serialize)]
该宏在编译时接收 SubscribeError 的 Token 作为输入,并返回另一个 Token 流作为输出即:它生成新的Rust代(derive的部分会被加上),然后被编译成最终的二进制文件。
在 #[derive(thiserror::Error)] 的上下文中,我们可以利用其他属性来实现之前的模版:
#[error(/* */)]定义了它所应用的枚举变量的显示表示。例如,当对SubscribeError::SendEmailError的case调用时,Display将返回Failed to send a confirmation email。你可以在最终显示中插值(插入String) → 例如,ValidationError上面的#[error("{0}")]中的{0}是指被包裹的String字段,模仿了访问元组结构上的字段的语法(即self.0)#[source]用来表示在Error::source中应该作为根本原因返回的东西#[from]为它所应用的类型自动派生出From的实现,使之可以转换为顶层的错误类型(例如,SubscribeError {/* */}的 impl From)。用#[from]注释的字段也被用作错误源,使我们不必在同一个字段上使用两个注释(例如,#[source] #[from] reqwest::Error,只需要标注一个即可)
我想提醒你注意一个小细节:我们没有为 ValidationError 变量使用 #[from]/#[source]。
这是因为 String 没有实现 Error Trait,因此它不能在 Error::source 中返回。这与我们之前手动实现 Error::source 时遇到的限制相同,这导致我们在 ValidationError 情况下返回 None。