39·错误处理进阶

anyhow 与 thiserror 实战

anyhow 与 thiserror 实战

学习目标

  1. 在库中使用 thiserror 定义错误
  2. 在应用中使用 anyhow 处理错误
  3. 掌握错误链和上下文

核心概念

库代码(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!()条件断言
{:#}显示完整错误链

练习编辑器

rust
Loading...