「这是我参与2022首次更文挑战的第 12 天,活动详情查看:2022首次更文挑战」。
使用 anyhow 作为不透明的错误类型
我们可以花更多的时间来打磨我们刚刚的代码,但事实证明这是没有必要的:我们可以再次看看crate生态系统中提供给我们什么 → thiserror 的作者为我们准备了另一个crate: anyhow。
#! Cargo.toml
[dependencies]
# [...]
anyhow = "1"
我们要找的类型是 anyhow::Error。引用文档中的话:
anyhow::Error是对动态错误类型的封装。anyhow::Error的工作方式很像Box<dyn std::error::Error>,但有这些区别: 1.anyhow::Error要求错误是Send、Sync和'static的。 2.anyhow::Error保证有一个backtrace,即使底层的错误类型不提供回溯。 3.anyhow::Error被表示为一个瘦指针 → 正好是一个字的大小,而不是两个。
额外的约束(Send、Sync和'static)对我们来说不是问题。我们很欣赏这种更紧凑的表示方式,以及可以访问 backtrace 的选项。
让我们在 SubscribeError 中用 anyhow::Error 替换 Box<dyn std::error:Error>。
//! src/routes/subscriptions.rs
// [...]
#[derive(thiserror::Error)]
pub enum SubscribeError {
#[error("{0}")]
ValidationError(String),
#[error(transparent)]
UnexpectedError(#[from] anyhow::Error),
}
impl ResponseError for SubscribeError {
fn status_code(&self) -> StatusCode {
match self {
// [...]
// Back to a single field
SubscribeError::UnexpectedError(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
在 SubscribeError::UnexpectedError 中,我们也甩掉了第二个String字段。
**anyhow::Error 提供了用 额外的上下文 来丰富一个错误的能力。**
//! src/routes/subscriptions.rs
use anyhow::Context;
// [...]
pub async fn subscribe(/* */) -> Result<HttpResponse, SubscribeError> {
// [...]
let mut transaction = pool
.begin()
.await
.context("Failed to acquire a Postgres connection from the pool")?;
let subscriber_id = insert_subscriber(/* */)
.await
.context("Failed to insert new subscriber in the database.")?;
// [..]
store_token(/* */)
.await
.context("Failed to store the confirmation token for a new subscriber.")?;
transaction
.commit()
.await
.context("Failed to commit SQL transaction to store a new subscriber.")?;
send_confirmation_email(/* */)
.await
.context("Failed to send a confirmation email.")?;
// [...]
}
context 在这里履行双重职责:
- 它将我们的方法所返回的错误转换为
anyhow::Error - 它用额外上下文来丰富错误。
context 是由 Context trait 提供的 → anyhow 为 Result 实现了它,让我们可以访问一个流畅的API,以轻松处理各种易变的函数。