Rust:专业级错误处理工具 thiserror
详解
thiserror
是 Rust 中用于高效定义自定义错误类型的库,特别适合库开发。相比 anyhow
的应用级错误处理,thiserror
提供更精确的错误控制,让库用户能模式匹配具体错误。
📦 基本安装
在 Cargo.toml
中添加:
[dependencies]
thiserror = "1.0"
🧩 核心功能
1. 基础错误定义
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("格式化字符串")]
定义错误的显示信息:
#[error("Invalid value: {value} (allowed: {allowed_values:?})")]
InvalidValue {value: i32,allowed_values: Vec<i32>,
}
调用:
println!("{}", MyError::InvalidValue {value: 42,allowed_values: vec![1, 2, 3]
});
// 输出: Invalid value: 42 (allowed: [1, 2, 3])
2. #[source]
标记错误来源(自动实现 Error::source
):
#[derive(Error, Debug)]
#[error("Config load failed")]
struct ConfigError {#[source] // 标记错误来源字段source: std::io::Error,
}
3. #[from]
自动实现 From
转换:
#[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::Intlet y: f64 = parts[1].parse()?; // 自动转为 ParseError::FloatOk((x as f64) * y)
}
4. #[backtrace]
自动捕获回溯信息:
#[derive(Error, Debug)]
#[error("Connection failed")]
struct ConnectionError {#[backtrace] // 自动记录回溯source: std::io::Error,
}
📚 结构体错误定义
#[derive(Error, Debug)]
#[error("Database error (code {code}): {message}")]
struct DbError {code: u32,message: String,#[source]inner: diesel::result::Error, // 底层错误
}
🔀 错误转换
#[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::Httpsave_to_db(&data)?; // DbError -> AppError::DbOk(())
}
⚡ 实用技巧
1. 添加额外上下文
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. 条件性字段
#[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. 组合使用宏
fn parse_number(s: &str) -> Result<i32, ParseError> {s.parse().map_err(|e| {// 添加上下文信息ParseError::InvalidFormat {input: s.to_string(),#[source] e}})
}
💡 最佳实践
- 库开发优先:在编写供他人使用的库时使用
thiserror
- 精准错误类型:使用枚举覆盖所有可能错误
- 丰富错误信息:通过格式化字符串暴露有用信息
- 区分层级:
#[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() |
性能 | 更高效(无堆分配) | 错误路径有堆分配 |
当需要同时使用:
[dependencies] anyhow = "1.0" thiserror = "1.0"
完整文档参考:thiserror on crates.io