青蛙小白
目录

anyhow

这个库提供了anyhow::Error,用于在Rust应用程序中实现简单且符合语言习惯的错误处理。

使用:

cargo add anyhow

anyhow::Result

anyhow::Result实际上是一个别名:

pub type Result<T, E = Error> = core::result::Result<T, E>;

使用Result<T, anyhow::Error>或等效的anyhow::Result<T>作为任何可能失败的函数的返回类型。

在函数内部,使用?运算符可以轻松地传播任何实现了std::error::Error trait的错误。

use anyhow::Result;

fn get_cluster_info() -> Result<ClusterMap> {
    let config = std::fs::read_to_string("cluster.json")?;
    let map: ClusterMap = serde_json::from_str(&config)?;
    Ok(map)
}

添加上下文信息

添加上下文信息来帮助排查错误的人理解问题出在哪里。像"找不到该文件或目录"这样的底层错误如果缺少应用程序当时正在执行的高层步骤的上下文信息,会让调试变得很烦人。

anyhow::Context是一个trait,用于在处理错误时,添加上下文信息,使得错误信息更加清晰易读,便于调试和排查问题。

anyhow为core::result::Result实现了anyhow::Context trait:

pub trait Context<T, E>: context::private::Sealed {
    /// Wrap the error value with additional context.
    fn context<C>(self, context: C) -> Result<T, Error>
    where
        C: Display + Send + Sync + 'static;

    /// Wrap the error value with additional context that is evaluated lazily
    /// only once an error does occur.
    fn with_context<C, F>(self, f: F) -> Result<T, Error>
    where
        C: Display + Send + Sync + 'static,
        F: FnOnce() -> C;
}

当代码遇到错误时,尤其是在进行复杂的函数调用或库操作时,可能得到的错误信息比较模糊或不明确。此时,可以通过 anyhow::Context 为错误添加更具体的上下文信息(例如:什么操作出错了、为什么会出错)。这不仅帮助开发者快速定位问题,也让错误报告更加友好。

anyhow 库的 Context trait 提供了两种方式来添加上下文信息:

  • context():为错误添加一条自定义消息。
  • with_context():使用闭包动态生成上下文信息,适合更复杂的情况。

anyhow文档中的例子:

use anyhow::{Context, Result};

fn main() -> Result<()> {
    ...
    it.detach().context("Failed to detach the important thing")?;

    let content = std::fs::read(path)
        .with_context(|| format!("Failed to read instrs from {}", path))?;
    ...
}
Error: Failed to read instrs from ./path/to/instrs.json

Caused by:
    No such file or directory (os error 2)

如何选择:

  • 当上下文信息是固定的、无论何时发生错误都一样时,使用 context 即可。
  • 当需要在错误发生时动态获取一些上下文信息,比如当前变量值、时间戳、文件路径等,则可以选择 with_context

Downcasting

支持降级转换(downcasting),可以根据需要通过值、共享引用或可变引用来进行转换。

// If the error was caused by redaction, then return a
// tombstone instead of the content.
match root_cause.downcast_ref::<DataStoreError>() {
    Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
    None => Err(error),
}

BACKTRACE信息

  • 如果使用 Rust 1.65 及以上版本,在底层错误类型没有提供自己的回溯信息时,错误会捕获并打印回溯信息。要查看回溯信息,需要通过std::backtrace中描述的环境变量来启用:
    • 如果想让panic和错误都有回溯信息,设置RUST_BACKTRACE=1
    • 如果只想让错误有回溯信息,设置RUST_LIB_BACKTRACE=1
    • 如果只想让panic有回溯信息,设置RUST_BACKTRACE=1RUST_LIB_BACKTRACE=0

derive(Error)

Anyhow可以与任何实现了std::error::Error的错误类型一起使用,包括在其他的crate中定义的错误类型。anyhow并没有提供derive(Error)宏,但你可以自己编写实现,或使用独立的宏,例如如thiserror

use thiserror::Error;

#[derive(Error, Debug)]
pub enum FormatError {
    #[error("Invalid header (expected {expected:?}, got {found:?})")]
    InvalidHeader {
        expected: String,
        found: String,
    },
    #[error("Missing attribute: {0}")]
    MissingAttribute(String),
}

anyhow!

可以使用anyhow!宏构造一次性错误消息,该宏支持字符串插值并生成anyhow::Error

return Err(anyhow!("Missing attribute: {}", missing));

bail!宏是提供的一种快捷方式,用于实现相同的提前返回功能。

bail!("Missing attribute: {}", missing);

与thiserror的比较

如果你不在乎函数返回的错误类型,只要它易于使用,请使用anyhow,这在应用程序代码中很常见。 如果你是在开发一个库,想要设计自己的专用错误类型,以便在失败时调用者获得您选择的准确信息,请使用thiserror。

评论