当前位置: 欣欣网 > 码农

Rust 错误处理:thiserror vs. anyhow

2024-08-29码农

在 Rust 的世界中,错误处理是构建健壮且可靠应用程序的基石。与其他语言不同,Rust 将错误视为程序执行过程中不可或缺的一部分,并提供强大的工具来处理它们。其中, thiserror anyhow 这两个库脱颖而出,为开发者提供了优雅且高效的错误处理机制。

thiserror:为库作者量身定制的错误类型定义利器

thiserror 的设计初衷是简化自定义错误类型的定义,它提供了一个方便的派生宏,用于实现标准库 std::error::Error trait。

thiserror 的核心功能

  1. 便捷的错误类型定义 : 使用 #[derive(Error, Debug)] 注解,可以轻松地为枚举或结构体实现 Error trait,从而将其转换为自定义错误类型。

use thiserror::Error;
#[derive(Error, Debug)]
pubenumMyError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
#[error("Custom error: {msg}")]
Custom { msg: String },
}

  1. 灵活的错误信息格式化 : #[error("...")] 属性支持占位符,可以方便地引用错误类型中的字段,生成结构化的错误信息。

#[derive(Error, Debug)]
pubenumFormatExample {
#[error("Error code {code}")]
Simple { code: i32 },
#[error("Complex error: {msg} (code: {code})")]
Complex { msg: String, code: i32 },
#[error("Error with source: {0}")]
WithSource(#[source] AnotherError),
}

  1. 自动实现 From trait : #[from] 属性可以自动实现 From trait,方便地将其他错误类型转换为自定义错误类型。

#[derive(Error, Debug)]
pubenumMyError {
// ...
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
// ...
}

thiserror 与 Anyhow 的比较

thiserror 侧重于为库作者提供定义明确的错误类型,而 anyhow 更适合在应用程序开发中处理各种可能的错误情况。

thiserror 的局限性

  • 仅适用于枚举和结构体。

  • 不支持泛型错误类型,但可以在枚举变体中使用泛型。

  • anyhow:应用程序级别错误处理的利器

    anyhow 提供了 anyhow::Error ,这是一个基于 trait 的错误类型,用于在 Rust 应用程序中轻松处理各种错误。

    anyhow 的主要功能

    1. 统一的错误处理 : 使用 Result<T, anyhow::Error> anyhow::Result<T> 作为任何可能失败的函数的返回类型,并使用 ? 运算符轻松地传播任何实现 std::error::Error trait 的错误。

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

    1. 丰富的错误上下文 : 使用 context with_context 方法为低级错误添加更多上下文信息,有助于故障排除。

    use anyhow::{Context, Result};
    fnmain() -> 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))?;
    // ...
    }

    1. 灵活的错误向下转换 : 支持按值、共享引用或可变引用进行向下转换。

    // 如果错误是由 redaction 引起的,则返回一个
    // tombstone 而不是内容。
    match root_cause.downcast_ref::<DataStoreError>() {
    Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
    None => Err(error),
    }

    1. 自动回溯 : 在 Rust 1.65 及更高版本中,如果底层错误类型没有提供自己的回溯, anyhow 将捕获并打印回溯。

    2. 与自定义错误类型集成 : anyhow 可以与任何实现 std::error::Error 的错误类型一起使用,包括在你的 crate 中定义的错误类型。

    use thiserror::Error;
    #[derive(Error, Debug)]
    pubenumFormatError {
    #[error("Invalid header (expected {expected:?}, got {found:?})")]
    InvalidHeader {
    expected: String,
    found: String,
    },
    #[error("Missing attribute: {0}")]
    MissingAttribute(String),
    }

    1. 便捷的错误构建 : 提供 anyhow! 宏用于构建一次性错误消息,支持字符串插值;还提供 bail! 宏作为提前返回错误的简写。

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

    1. no_std 支持 : anyhow 支持 no_std 模式,几乎所有 API 都可用并以相同的方式工作。

    弃用的错误处理库

    一些曾经广泛使用的库,例如 failure error-chain ,由于长期缺乏维护,现在基本上已经被废弃。

    总结

    thiserror anyhow 是 Rust 生态系统中两个优秀的错误处理库,它们为开发者提供了强大的工具来构建健壮且可靠的应用程序。 thiserror 专注于为库作者提供便捷的自定义错误类型定义方式,而 anyhow 则致力于简化应用程序级别的错误处理。选择合适的工具取决于具体的应用场景。

    文章精选

    「Rust