Rust:专业级错误处理工具 thiserror
详解
thiserror
是 Rust 中用于高效定义自定义错误类型 的库,特别适合库开发。相比 anyhow
的应用级错误处理,thiserror
提供更精确的错误控制,让库用户能模式匹配具体错误。
📦 基本安装
在 Cargo.toml
中添加:
toml
[dependencies]
thiserror = "1.0"
🧩 核心功能
1. 基础错误定义
rust
use thiserror::Error;
#[derive(Error, Debug)]
enum MyError {
#[error("File not found: {0}")]
NotFound(String),
#[error("I/O error occurred")]
Io(#[from] std::io::Error),
#[error("Validation failed for {field}: {reason}")]
Validation {
field: &'static str,
reason: String,
},
}
2. 自动实现特征
自动为你的类型实现:
std::error::Error
Display
(通过#[error]
属性)From
(通过#[from]
属性)
🛠️ 属性详解
1. #[error("格式化字符串")]
定义错误的显示信息:
rust
#[error("Invalid value: {value} (allowed: {allowed_values:?})")]
InvalidValue {
value: i32,
allowed_values: Vec<i32>,
}
调用:
rust
println!("{}", MyError::InvalidValue {
value: 42,
allowed_values: vec![1, 2, 3]
});
// 输出: Invalid value: 42 (allowed: [1, 2, 3])
2. #[source]
标记错误来源(自动实现 Error::source
):
rust
#[derive(Error, Debug)]
#[error("Config load failed")]
struct ConfigError {
#[source] // 标记错误来源字段
source: std::io::Error,
}
3. #[from]
自动实现 From
转换:
rust
#[derive(Error, Debug)]
enum ParseError {
#[error("Integer parsing failed")]
Int(#[from] std::num::ParseIntError),
#[error("Float parsing failed")]
Float(#[from] std::num::ParseFloatError),
}
// 自动转换
fn parse(s: &str) -> Result<f64, ParseError> {
let parts: Vec<&str> = s.split(':').collect();
let x: i32 = parts[0].parse()?; // 自动转为 ParseError::Int
let y: f64 = parts[1].parse()?; // 自动转为 ParseError::Float
Ok((x as f64) * y)
}
4. #[backtrace]
自动捕获回溯信息:
rust
#[derive(Error, Debug)]
#[error("Connection failed")]
struct ConnectionError {
#[backtrace] // 自动记录回溯
source: std::io::Error,
}
📚 结构体错误定义
rust
#[derive(Error, Debug)]
#[error("Database error (code {code}): {message}")]
struct DbError {
code: u32,
message: String,
#[source]
inner: diesel::result::Error, // 底层错误
}
🔀 错误转换
rust
#[derive(Error, Debug)]
enum AppError {
#[error("HTTP error: {0}")]
Http(#[from] HttpError),
#[error("Database error")]
Db(#[from] DbError),
}
fn handle_request() -> Result<(), AppError> {
let data = fetch_data()?; // HttpError -> AppError::Http
save_to_db(&data)?; // DbError -> AppError::Db
Ok(())
}
⚡ 实用技巧
1. 添加额外上下文
rust
fn read_config() -> Result<Config, MyError> {
let path = "config.toml";
let content = std::fs::read_to_string(path)
.map_err(|e| MyError::Io(e).context(format!("Failed to read {}", path)))?;
// ...
}
2. 条件性字段
rust
#[derive(Error, Debug)]
#[error("Operation failed{}{}", .details.as_ref().map(|s| format!(": {}", s)).unwrap_or_default())]
struct OpError {
details: Option<String>,
#[source]
source: anyhow::Error,
}
3. 组合使用宏
rust
fn parse_number(s: &str) -> Result<i32, ParseError> {
s.parse().map_err(|e| {
// 添加上下文信息
ParseError::InvalidFormat {
input: s.to_string(),
#[source] e
}
})
}
💡 最佳实践
-
库开发优先 :在编写供他人使用的库时使用
thiserror
-
精准错误类型:使用枚举覆盖所有可能错误
-
丰富错误信息:通过格式化字符串暴露有用信息
-
区分层级 :
rust#[derive(Error, Debug)] enum ApiError { #[error(transparent)] Request(#[from] RequestError), #[error(transparent)] Parsing(#[from] ParseError), #[error("Authentication failed")] Auth, }
⚠️ 常见错误解决
问题 :#[derive(Error)]
后未实现 Display
解决 :确保每个变体都有 #[error]
属性
问题 :source
字段不工作
解决:
- 添加
#[source]
或#[from]
属性 - 确保字段类型实现了
std::error::Error
🆚 thiserror vs anyhow
特性 | thiserror | anyhow |
---|---|---|
适用场景 | 库开发 | 应用开发 |
错误类型 | 强类型自定义错误 | 通用错误类型 (anyhow::Error ) |
模式匹配 | 支持精确匹配 | 只支持粗略匹配 |
上下文添加 | 需手动实现 | 内置 .context() |
性能 | 更高效(无堆分配) | 错误路径有堆分配 |
当需要同时使用:
toml[dependencies] anyhow = "1.0" thiserror = "1.0"
完整文档参考:thiserror on crates.io