m06-error-handling by actionbook/rust-skills
npx skills add https://github.com/actionbook/rust-skills --skill m06-error-handling第 1 层:语言机制
这个失败是预期的还是一个错误?
在选择错误处理策略之前:
| 模式 | 不要只说 | 而是应该问 |
|---|---|---|
| unwrap 导致恐慌 | "使用 ?" | 这里 None/Err 真的可能发生吗? |
| ? 的类型不匹配 | "使用 anyhow" | 错误类型设计正确吗? |
| 丢失错误上下文 | "添加 .context()" | 调用者需要知道什么? |
| 错误变体太多 | "使用 Box" | 错误的粒度合适吗? |
在处理错误之前:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
谁来处理这个错误?
需要什么上下文信息?
当错误策略不明确时:
"我应该返回 Result 还是 Option?"
↑ 问:缺失/失败是正常的还是异常的?
↑ 检查:m09-domain(领域模型怎么说?)
↑ 检查:domain-*(错误处理要求)
| 情况 | 追溯到 | 问题 |
|---|---|---|
| 太多 unwrap | m09-domain | 数据模型正确吗? |
| 错误上下文设计 | m13-domain-error | 需要什么恢复操作? |
| 库错误 vs 应用错误 | m11-ecosystem | 消费者是谁? |
从设计到实现:
"预期失败,库代码"
↓ 使用: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") |
| 上下文 | 错误处理库 | 原因 |
|---|---|---|
| 库 | thiserror | 为消费者提供类型化错误 |
| 应用 | anyhow | 符合人体工学的错误处理 |
| 混合 | 两者都用 | 在边界使用 thiserror,内部使用 anyhow |
失败是预期的吗?
├─ 是 → 缺失是唯一的"失败"吗?
│ ├─ 是 → Option<T>
│ └─ 否 → Result<T, E>
│ ├─ 库 → thiserror
│ └─ 应用 → anyhow
└─ 否 → 这是一个错误吗?
├─ 是 → panic!, assert!
└─ 否 → 考虑是否真的不可恢复
使用 ? → 需要上下文吗?
├─ 是 → .context("message")
└─ 否 → 直接使用 ?
| 错误 | 原因 | 修复方法 |
|---|---|---|
unwrap() 恐慌 | 未处理的 None/Err | 使用 ? 或 match |
| 类型不匹配 | 不同的错误类型 | 使用 anyhow 或 From |
| 丢失上下文 | 没有上下文的 ? | 添加 .context() |
cannot use ? | 缺少 Result 返回类型 | 返回 Result<(), E> |
| 反模式 | 为什么不好 | 更好的做法 |
|---|---|---|
到处使用 .unwrap() | 在生产环境中恐慌 | .expect("reason") 或 ? |
| 静默忽略错误 | 隐藏错误 | 处理或传播错误 |
对预期错误使用 panic! | 用户体验差,无法恢复 | 使用 Result |
| 到处使用 Box | 丢失类型信息 | 使用 thiserror |
| 时机 | 参见 |
|---|---|
| 领域错误策略 | m13-domain-error |
| crate 边界 | m11-ecosystem |
| 类型安全错误 | m05-type-driven |
| 心智模型 | m14-mental-model |
每周安装量
42
代码仓库
GitHub 星标数
809
首次出现
2026 年 1 月 23 日
安全审计
安装于
gemini-cli37
codex37
opencode36
claude-code33
github-copilot31
cursor30
Layer 1: Language Mechanics
Is this failure expected or a bug?
Before choosing error handling strategy:
| Pattern | Don't Just Say | Ask Instead |
|---|---|---|
| unwrap panics | "Use ?" | Is None/Err actually possible here? |
| Type mismatch on ? | "Use anyhow" | Are error types designed correctly? |
| Lost error context | "Add .context()" | What does the caller need to know? |
| Too many error variants | "Use Box" | Is error granularity right? |
Before handling an error:
What kind of failure is this?
Who handles this?
What context is needed?
When error strategy is unclear:
"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)
| Situation | Trace To | Question |
|---|---|---|
| Too many unwraps | m09-domain | Is the data model right? |
| Error context design | m13-domain-error | What recovery is needed? |
| Library vs app errors | m11-ecosystem | Who are the consumers? |
From design to implementation:
"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")
| Pattern | When | Example |
|---|---|---|
Result<T, E> | Recoverable error | fn read() -> Result<String, io::Error> |
Option<T> | Absence is normal | fn find() -> Option<&Item> |
? | Propagate error | let data = file.read()?; |
unwrap() |
| Context | Error Crate | Why |
|---|---|---|
| Library | thiserror | Typed errors for consumers |
| Application | anyhow | Ergonomic error handling |
| Mixed | Both | thiserror at boundaries, anyhow internally |
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 ?
| Error | Cause | Fix |
|---|---|---|
unwrap() panic | Unhandled None/Err | Use ? or match |
| Type mismatch | Different error types | Use anyhow or From |
| Lost context | ? without context | Add .context() |
cannot use ? |
| Anti-Pattern | Why Bad | Better |
|---|---|---|
.unwrap() everywhere | Panics in production | .expect("reason") or ? |
| Ignore errors silently | Bugs hidden | Handle or propagate |
panic! for expected errors | Bad UX, no recovery | Result |
| Box everywhere | Lost type info | thiserror |
| When | See |
|---|---|
| Domain error strategy | m13-domain-error |
| Crate boundaries | m11-ecosystem |
| Type-safe errors | m05-type-driven |
| Mental models | m14-mental-model |
Weekly Installs
42
Repository
GitHub Stars
809
First Seen
Jan 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
gemini-cli37
codex37
opencode36
claude-code33
github-copilot31
cursor30
Rust 开发模式与最佳实践:构建安全高性能应用程序的惯用模式指南
1,100 周安装
| Dev/test only |
config.get("key").unwrap() |
expect() | Invariant holds | env.get("HOME").expect("HOME set") |
panic! | Unrecoverable | panic!("critical failure") |
| Missing Result return |
Return Result<(), E> |