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