「深挖Rust」错误处理 — 5

164 阅读3分钟

「这是我参与2022首次更文挑战的第 6 天,活动详情查看:2022首次更文挑战」。


让我们在 StoreTokenError 实现一下:

//! src/routes/subscriptions.rs
// [...]

// We derive `Debug`, easy and painless.
#[derive(Debug)]
pub struct StoreTokenError(sqlx::Error);

impl std::fmt::Display for StoreTokenError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "A database error was encountered while \
            trying to store a subscription token."
        )
    }
}

编译通过!我们现在可以在我们的请求处理程序中利用它了。

//! src/routes/subscriptions.rs
// [...]

pub async fn subscribe(/* */) -> Result<HttpResponse, actix_web::Error> {
    // You will have to wrap (early) returns in `Ok(...)` as well!
    // [...]
    // The `?` operator transparently invokes the `Into` trait
    // on our behalf - we don't need an explicit `map_err` anymore.
    store_token(/* */).await?;
    // [...]
}

我们现在再看一下我们的日志的显示:

# sqlx logs are a bit spammy, cutting them out to reduce noise
export RUST_LOG="sqlx=error,info"
export TEST_LOG=enabled
cargo t subscribe_fails_if_there_is_a_fatal_database_error | bunyan
...
 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

这样看起来就好多了嘛!

在请求处理结束时输出的日志记录现在包含了对导致应用程序向用户返回500内部服务器错误:

  • 错误输出栈
  • 简要描述

只要看一下这个日志记录,就足以对这个请求的所有事项有一个相当准确的了解。

Error Trait

到目前为止,我们通过遵循编译器的建议前进,试图满足 actix-web 在错误处理方面对我们的约束。让我们回过头来看看更大的场景:在Rust中,错误应该是什么样子的(不考虑actix-web的具体情况)?

Rust的标准库有一个专门的Trait: Error

pub trait Error: Debug + Display {
    /// The lower-level source of this error, if any.
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        None
    }
}

它需要实现 Debug & Display,就像 ResponseError 一样。它还为我们提供了实现 source 的选择,该方法可以返回错误的根本原因(如果有的话)。

对于我们的错误类型,实现 Error Trait的意义在哪呢?它不是Result所要求的 → 任何类型都可以作为错误变量。

pub enum Result<T, E> {
    /// Contains the success value
    Ok(T),

    /// Contains the error value
    Err(E),
}

Error Trait 首先在语义上将我们的类型标记为错误。它可以帮助我们代码库的读者立即发现其设计的目的。这也是Rust社区对良好错误的最低要求进行标准化的一种方式:

  • 它应该提供不同的表示(Debug & Display),针对不同的受众进行调整;
  • 应该可以查看错误的根本原因,如果有的话(source())

这个列表仍在发展中。例如,有一个不稳定的 backtrace()

错误处理是Rust社区中一个活跃的研究领域。如果你有兴趣了解下一步的发展,我强烈建议你关注Rust错误处理工作组的情况:here

通过对所有可选方法的实现,我们可以充分利用错误处理生态系统:即那些通常为处理错误而设计的函数。我们将在下面的几节中编写一个!