Rust错误处理指南 m06-error-handling

本指南是关于 Rust 编程语言中错误处理的系统性知识库。它详细阐述了如何区分预期失败与程序错误,并提供了从 Result、Option、panic 到 thiserror、anyhow 等库的完整决策流程。核心内容包括错误传播、上下文添加、库与应用的不同策略,以及常见反模式和最佳实践。关键词:Rust 错误处理,Result 用法,panic vs Result,thiserror,anyhow,错误传播,自定义错误,可恢复错误,不可恢复错误。

后端开发 0 次安装 0 次浏览 更新于 2/27/2026

名称:m06-错误处理 描述:“关键:用于错误处理。触发词:Result、Option、Error、?、unwrap、expect、panic、anyhow、thiserror、何时用 panic 对比返回 Result、自定义错误、错误传播、错误处理、Result 用法、什么时候用 panic” 用户可调用:false

错误处理

第 1 层:语言机制

核心问题

这个失败是预期的还是一个错误(bug)?

在选择错误处理策略之前:

  • 这在正常操作中会失败吗?
  • 谁应该处理这个失败?
  • 调用者需要什么上下文信息?

错误 → 设计问题

模式 不要只是说 而是问
unwrap 会 panic “用 ?” None/Err 在这里实际上可能发生吗?
? 的类型不匹配 “用 anyhow” 错误类型设计正确吗?
丢失错误上下文 “添加 .context()” 调用者需要知道什么?
错误变体太多 “用 Box<dyn Error>” 错误的粒度合适吗?

思考提示

在处理错误之前:

  1. 这是什么类型的失败?

    • 预期的 → Result<T, E>
    • 正常情况下的缺失 → Option<T>
    • 错误/不变量违反 → panic!
    • 不可恢复的 → panic!
  2. 谁来处理?

    • 调用者 → 用 ? 传播
    • 当前函数 → 用 match/if-let 处理
    • 用户 → 友好的错误信息
    • 程序员 → 带信息的 panic
  3. 需要什么上下文?

    • 错误类型 → thiserror 变体
    • 调用链 → anyhow::Context
    • 调试信息 → anyhow 或 tracing

向上追溯 ↑

当错误策略不明确时:

"我应该返回 Result 还是 Option?"
    ↑ 问:缺失/失败是正常的还是异常的?
    ↑ 检查:m09-领域(领域怎么说?)
    ↑ 检查:domain-*(错误处理要求)
情况 追溯到 问题
太多 unwrap m09-领域 数据模型正确吗?
错误上下文设计 m13-领域错误 需要什么恢复?
库 vs 应用错误 m11-生态系统 消费者是谁?

向下追溯 ↓

从设计到实现:

"预期失败,库代码"
    ↓ 使用:thiserror 用于类型化错误

"预期失败,应用代码"
    ↓ 使用:anyhow 用于符合人体工学的错误处理

"缺失是正常的(查找、获取、查询)"
    ↓ 使用:Option<T>

"错误或不变量违反"
    ↓ 使用:panic!, assert!, unreachable!

"需要带上下文传播"
    ↓ 使用:.context("发生了什么")

快速参考

模式 何时使用 示例
Result<T, E> 可恢复的错误 fn read() -> Result<String, io::Error>
Option<T> 缺失是正常的 fn find() -> Option<&Item>
? 传播错误 let data = file.read()?;
unwrap() 仅用于开发/测试 config.get("key").unwrap()
expect() 不变量成立 env.get("HOME").expect("HOME set")
panic! 不可恢复的 panic!("critical failure")

库 vs 应用

上下文 错误库 原因
thiserror 为消费者提供类型化错误
应用 anyhow 符合人体工学的错误处理
混合 两者 在边界用 thiserror,内部用 anyhow

决策流程图

失败是预期的吗?
├─ 是 → 缺失是唯一的“失败”吗?
│        ├─ 是 → Option<T>
│        └─ 否 → Result<T, E>
│                 ├─ 库 → thiserror
│                 └─ 应用 → anyhow
└─ 否 → 这是一个错误(bug)吗?
        ├─ 是 → panic!, assert!
        └─ 否 → 考虑是否真的不可恢复

使用 ? → 需要上下文吗?
├─ 是 → .context("message")
└─ 否 → 普通的 ?

常见错误

错误 原因 修复
unwrap() panic 未处理的 None/Err 使用 ? 或 match
类型不匹配 不同的错误类型 使用 anyhowFrom
丢失上下文 没有上下文的 ? 添加 .context()
cannot use ? 缺少 Result 返回类型 返回 Result<(), E>

反模式

反模式 为什么不好 更好的做法
到处用 .unwrap() 在生产环境中 panic .expect("reason")?
静默忽略错误 隐藏错误 处理或传播
对预期错误用 panic! 用户体验差,无法恢复 Result
到处用 Box<dyn Error> 丢失类型信息 thiserror

相关技能

何时 参见
领域错误策略 m13-领域错误
包边界 m11-生态系统
类型安全的错误 m05-类型驱动
心智模型 m14-心智模型