错误处理 crate 生态
问题
Rust 错误处理有哪些流行的 crate?如何选择?
答案
标准库提供 std::error::Error trait,但实际开发中几乎都要用第三方 crate 来简化错误处理。
关于 Rust 错误处理基础,请参考 错误处理。
thiserror:定义库错误
用于库开发,通过 derive 宏生成 Error + Display 实现:
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("数据库错误: {0}")]
Database(#[from] sqlx::Error),
#[error("找不到用户: {id}")]
UserNotFound { id: u64 },
#[error("认证失败")]
Unauthorized,
#[error("IO 错误")]
Io(#[from] std::io::Error),
}
anyhow:应用错误
用于应用开发,提供类型擦除的 anyhow::Error,无需定义枚举:
use anyhow::{Context, Result, bail, ensure};
fn read_config(path: &str) -> Result<Config> {
let content = std::fs::read_to_string(path)
.context(format!("读取配置文件失败: {}", path))?;
let config: Config = toml::from_str(&content)
.context("解析 TOML 失败")?;
ensure!(config.port > 0, "端口号必须为正数");
if config.name.is_empty() {
bail!("名称不能为空"); // 直接返回错误
}
Ok(config)
}
选择指南
| crate | 场景 | 特点 |
|---|---|---|
| thiserror | 库开发 | 类型化错误、#[from] 自动转换 |
| anyhow | 应用开发 | 类型擦除、.context() 链、快速开发 |
| eyre | anyhow 替代 | 可自定义报告格式、color-eyre |
| miette | CLI 工具 | 花哨的错误诊断报告 |
最佳实践
- 写库:用
thiserror定义结构化错误类型,让调用者可以 match - 写应用:用
anyhow快速传播错误,用.context()添加上下文 - 两者可以混用:库用 thiserror,应用入口用 anyhow
常见面试问题
Q1: thiserror 和 anyhow 能一起用吗?
答案:
可以且推荐。库层用 thiserror 定义精确的错误类型,应用层用 anyhow 统一处理:
// 库层
#[derive(thiserror::Error, Debug)]
pub enum DbError { /* ... */ }
// 应用层
fn main() -> anyhow::Result<()> {
let conn = db::connect()
.context("数据库连接失败")?; // DbError 自动转为 anyhow::Error
Ok(())
}
Q2: 什么时候不该用 anyhow?
答案:
- 写公共库时:调用者无法 match 具体错误类型
- 需要根据错误类型做不同处理时:类型擦除后无法精确匹配(虽然可以 downcast,但不推荐)