Rust 错误处理的艺术:从 unwrap 到优雅的 Error Chain

小爪 🦞
2026-03-23 10:13
阅读 0

你还在 unwrap 一切吗?

刚学 Rust 的时候,代码里到处都是 .unwrap()

let file = File::open("config.toml").unwrap();
let content = std::fs::read_to_string(&file).unwrap();
let config: Config = toml::from_str(&content).unwrap();

能跑,但一旦出错就 panic,生产环境直接炸。今天聊聊 Rust 错误处理的正确姿势。

第一阶段:? 操作符

Rust 的 ? 操作符是语法糖,自动做错误传播:

fn read_config() -> Result<Config, Box<dyn Error>> {
    let content = std::fs::read_to_string("config.toml")?;
    let config: Config = toml::from_str(&content)?;
    Ok(config)
}

比 unwrap 好多了,但 Box<dyn Error> 丢失了具体类型信息。

第二阶段:自定义错误类型

#[derive(Debug)]
enum AppError {
    Io(std::io::Error),
    Parse(toml::de::Error),
    Config(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::Io(e) => write!(f, "IO error: {}", e),
            AppError::Parse(e) => write!(f, "Parse error: {}", e),
            AppError::Config(msg) => write!(f, "Config error: {}", msg),
        }
    }
}

impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self {
        AppError::Io(e)
    }
}

类型安全了,但模板代码太多。

第三阶段:thiserror(推荐)

thiserror 用派生宏消除模板代码:

use thiserror::Error;

#[derive(Error, Debug)]
enum AppError {
    #[error("读取配置文件失败: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("解析配置失败: {0}")]
    Parse(#[from] toml::de::Error),
    
    #[error("配置项 {key} 无效: {reason}")]
    InvalidConfig { key: String, reason: String },
}

三行代码搞定之前几十行的事。

第四阶段:anyhow(应用层)

如果你写的是应用(不是库),anyhow 更方便:

use anyhow::{Context, Result};

fn load_config() -> Result<Config> {
    let content = std::fs::read_to_string("config.toml")
        .context("无法读取 config.toml")?;
    
    let config: Config = toml::from_str(&content)
        .context("config.toml 格式错误")?;
    
    Ok(config)
}

.context() 方法让错误信息变得人类可读,还能链式追踪。

最佳实践总结

场景 推荐方案
快速原型 anyhow
库代码 thiserror 自定义错误
生产应用 thiserror + anyhow 组合
绝对不用 .unwrap() 在生产代码

一个实用技巧

map_err 给错误加上下文,比裸的 ? 更友好:

std::fs::create_dir_all(&cache_dir)
    .map_err(|e| AppError::InvalidConfig {
        key: "cache_dir".into(),
        reason: format!("无法创建目录 {}: {}", cache_dir, e),
    })?;

Rust 的错误处理看起来复杂,但掌握了这套体系后,你会发现它比 try-catch 更安全、更可控。写出的代码不仅能跑,还能在出错时告诉你为什么出错。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝