「深挖Rust」错误处理 — 11

288 阅读2分钟

「这是我参与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 要求错误是 SendSync'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 提供的 → anyhowResult 实现了它,让我们可以访问一个流畅的API,以轻松处理各种易变的函数。