在 Rust 的世界中,错误处理是构建健壮且可靠应用程序的基石。与其他语言不同,Rust 将错误视为程序执行过程中不可或缺的一部分,并提供强大的工具来处理它们。其中,thiserror
和 anyhow
这两个库脱颖而出,为开发者提供了优雅且高效的错误处理机制。
thiserror:为库作者量身定制的错误类型定义利器
thiserror
的设计初衷是简化自定义错误类型的定义,它提供了一个方便的派生宏,用于实现标准库 std::error::Error
trait。
thiserror 的核心功能
-
便捷的错误类型定义: 使用
#[derive(Error, Debug)]
注解,可以轻松地为枚举或结构体实现Error
trait,从而将其转换为自定义错误类型。use thiserror::Error; #[derive(Error, Debug)] pub enum MyError { #[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 }, }
-
灵活的错误信息格式化:
#[error("...")]
属性支持占位符,可以方便地引用错误类型中的字段,生成结构化的错误信息。#[derive(Error, Debug)] pub enum FormatExample { #[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), }
-
自动实现
From
trait:#[from]
属性可以自动实现From
trait,方便地将其他错误类型转换为自定义错误类型。#[derive(Error, Debug)] pub enum MyError { // ... #[error("IO error: {0}")] Io(#[from] std::io::Error), // ... }
thiserror 与 Anyhow 的比较
thiserror
侧重于为库作者提供定义明确的错误类型,而 anyhow
更适合在应用程序开发中处理各种可能的错误情况。
thiserror 的局限性
- 仅适用于枚举和结构体。
- 不支持泛型错误类型,但可以在枚举变体中使用泛型。
anyhow:应用程序级别错误处理的利器
anyhow
提供了 anyhow::Error
,这是一个基于 trait 的错误类型,用于在 Rust 应用程序中轻松处理各种错误。
anyhow 的主要功能
-
统一的错误处理: 使用
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) }
-
丰富的错误上下文: 使用
context
或with_context
方法为低级错误添加更多上下文信息,有助于故障排除。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))?; // ... }
-
灵活的错误向下转换: 支持按值、共享引用或可变引用进行向下转换。
// 如果错误是由 redaction 引起的,则返回一个 // tombstone 而不是内容。 match root_cause.downcast_ref::<DataStoreError>() { Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)), None => Err(error), }
-
自动回溯: 在 Rust 1.65 及更高版本中,如果底层错误类型没有提供自己的回溯,
anyhow
将捕获并打印回溯。 -
与自定义错误类型集成:
anyhow
可以与任何实现std::error::Error
的错误类型一起使用,包括在你的 crate 中定义的错误类型。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!
宏用于构建一次性错误消息,支持字符串插值;还提供bail!
宏作为提前返回错误的简写。return Err(anyhow!("Missing attribute: {}", missing)); bail!("Missing attribute: {}", missing);
-
no_std 支持:
anyhow
支持no_std
模式,几乎所有 API 都可用并以相同的方式工作。
弃用的错误处理库
一些曾经广泛使用的库,例如 failure
和 error-chain
,由于长期缺乏维护,现在基本上已经被废弃。
总结
thiserror
和 anyhow
是 Rust 生态系统中两个优秀的错误处理库,它们为开发者提供了强大的工具来构建健壮且可靠的应用程序。thiserror
专注于为库作者提供便捷的自定义错误类型定义方式,而 anyhow
则致力于简化应用程序级别的错误处理。选择合适的工具取决于具体的应用场景。