anyhow 与 thiserror 实战
学习目标
- 在库中使用 thiserror 定义错误
- 在应用中使用 anyhow 处理错误
- 掌握错误链和上下文
核心概念
库代码(thiserror)
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("配置文件未找到: {path}")]
NotFound { path: String },
#[error("解析失败: {0}")]
Parse(#[from] serde_json::Error),
#[error("IO 错误: {0}")]
Io(#[from] std::io::Error),
}
pub fn load_config(path: &str) -> Result<Config, ConfigError> {
let content = std::fs::read_to_string(path)?;
let config: Config = serde_json::from_str(&content)?;
Ok(config)
}
应用代码(anyhow)
use anyhow::{Context, Result, bail, ensure};
fn run() -> Result<()> {
let config = load_config("config.json")
.context("加载配置失败")?;
ensure!(config.port > 0, "端口必须大于 0");
if config.name.is_empty() {
bail!("名称不能为空");
}
Ok(())
}
fn main() {
if let Err(e) = run() {
eprintln!("错误: {:#}", e);
}
}
anyhow 常用功能
use anyhow::{anyhow, Context, Result};
fn example() -> Result<()> {
let err = anyhow!("something went wrong");
let content = std::fs::read_to_string("file.txt")
.context("无法读取文件")?;
let value: i32 = content.trim().parse()
.context(format!("解析 '{}' 失败", content.trim()))?;
Ok(())
}
实践练习
练习 1:完整应用
use anyhow::{Context, Result};
use std::collections::HashMap;
fn read_scores(path: &str) -> Result<HashMap<String, f64>> {
let content = std::fs::read_to_string(path)
.context(format!("读取分数文件 '{}'", path))?;
let mut scores = HashMap::new();
for (i, line) in content.lines().enumerate() {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() != 2 {
anyhow::bail!("第 {} 行格式错误: '{}'", i + 1, line);
}
let name = parts[0].trim().to_string();
let score: f64 = parts[1].trim().parse()
.context(format!("第 {} 行分数解析失败", i + 1))?;
scores.insert(name, score);
}
Ok(scores)
}
fn main() -> Result<()> {
let scores = read_scores("scores.csv")?;
for (name, score) in &scores {
println!("{}: {}", name, score);
}
Ok(())
}
小结
| 工具 | 用途 |
|---|
thiserror | 定义错误类型(#[derive(Error)]) |
anyhow | 快速错误处理(Result<T>) |
.context() | 添加上下文信息 |
bail!() | 提前返回错误 |
ensure!() | 条件断言 |
{:#} | 显示完整错误链 |