关于 m06-error-handling
面向 Rust 开发者的错误处理技能,帮助为库和应用设计失败处理方式。它区分预期失败与程序错误,并指导选择合适的类型、传播方式和上下文信息。
主要功能
- 01区分预期失败与程序错误
- 02选择 Result、Option 或 panic
- 03比较库和应用的错误处理模式
- 04传播错误时保留上下文
适用场景
- 01设计 API 的错误返回类型
- 02替换不安全的 unwrap 用法
- 03为传播的错误补充上下文
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ actionbook/ rust-skills --skill m06-error-handling安装后在 Claude Code 中输入/m06-error-handling确认已加载。
附带文件examples/library-vs-app.mdpatterns/error-patterns.md
详细介绍
错误处理
第 1 层:语言机制
核心问题
这是预期内的失败,还是 Bug?
在选择错误处理策略之前:
- 这在正常运行中可能发生吗?
- 谁应该处理这个失败?
- 调用方需要哪些上下文?
错误 → 设计问题
| 模式 | 不要只说 | 应该问 |
|---|---|---|
| unwrap 引发 panic | “用 ?” | 这里真的可能是 None/Err 吗? |
| ? 上的类型不匹配 | “用 anyhow” | 错误类型的设计是否合理? |
| 错误上下文丢失 | “加上 .context()” | 调用方需要了解什么? |
| 错误变体过多 | “用 Box” | 错误粒度是否合适? |
思考提示
处理错误之前:
-
这是什么类型的失败?
- 预期内 → Result<T, E>
- 缺失是正常情况 → Option
- Bug/不变量被破坏 → panic!
- 不可恢复 → panic!
-
由谁处理?
- 调用方 → 使用 ? 传播
- 当前函数 → 使用 match/if-let
- 用户 → 显示友好的错误消息
- 程序员 → 使用消息触发 panic
-
需要哪些上下文?
- 错误类型 → thiserror 变体
- 调用链 → anyhow::Context
- 调试信息 → anyhow 或 tracing
向上追溯 ↑
错误策略不明确时:
"Should I return Result or Option?"
↑ Ask: Is absence/failure normal or exceptional?
↑ Check: m09-domain (what does domain say?)
↑ Check: domain-* (error handling requirements)
| 情况 | 追溯到 | 问题 |
|---|---|---|
| unwrap 过多 | m09-domain | 数据模型是否合理? |
| 错误上下文设计 | m13-domain-error | 需要怎样的恢复方式? |
| 库错误与应用错误 | m11-ecosystem | 谁会使用它们? |
向下追溯 ↓
从设计到实现:
"Expected failure, library code"
↓ Use: thiserror for typed errors
"Expected failure, application code"
↓ Use: anyhow for ergonomic errors
"Absence is normal (find, get, lookup)"
↓ Use: Option<T>
"Bug or invariant violation"
↓ Use: panic!, assert!, unreachable!
"Need to propagate with context"
↓ Use: .context("what was happening")
快速参考
| 模式 | 适用情况 | 示例 |
|---|---|---|
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") |
库与应用程序
| 上下文 | 错误 Crate | 原因 |
|---|---|---|
| 库 | thiserror | 为使用者提供有类型的错误 |
| 应用程序 | anyhow | 便捷的错误处理 |
| 混合 | 两者 | 在边界处使用 thiserror,内部使用 anyhow |
决策流程图
Is failure expected?
├─ Yes → Is absence the only "failure"?
│ ├─ Yes → Option<T>
│ └─ No → Result<T, E>
│ ├─ Library → thiserror
│ └─ Application → anyhow
└─ No → Is it a bug?
├─ Yes → panic!, assert!
└─ No → Consider if really unrecoverable
Use ? → Need context?
├─ Yes → .context("message")
└─ No → Plain ?
常见错误
| 错误 | 原因 | 修复方法 |
|---|---|---|
unwrap() panic | 未处理 None/Err | 使用 ? 或 match |
| 类型不匹配 | 错误类型不同 | 使用 anyhow 或 From |
| 上下文丢失 | ? 未附加上下文 | 添加 .context() |
cannot use ? | 缺少 Result 返回类型 | 返回 Result<(), E> |
反模式
| 反模式 | 不妥之处 | 更好的做法 |
|---|---|---|
到处使用 .unwrap() | 在生产环境中触发 panic | 使用 .expect("reason") 或 ? |
| 默默忽略错误 | 隐藏 Bug | 处理错误或传播错误 |
对预期内的错误使用 panic! | 用户体验差,无法恢复 | 使用 Result |
| 到处使用 Box | 类型信息丢失 | 使用 thiserror |
相关技能
| 适用情况 | 参见 |
|---|---|
| 领域错误策略 | m13-domain-error |
| Crate 边界 | m11-ecosystem |
| 类型安全的错误 | m05-type-driven |
| 心智模型 | m14-mental-model |
评论
支持 Markdown