Rust-skills rust-error-advanced
深入错误处理专家。处理 Result vs Option, thiserror, anyhow, error context, library vs application errors, 错误处理, Result 用法, 什么时候用 panic--- # 深入错误处理 ## 核心问题 **这个失败是预期的还是 bug?** 错误处理策略决定代码的健壮性。
install
source · Clone the upstream repo
git clone https://github.com/huiali/rust-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/huiali/rust-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/rust-error-advanced" ~/.claude/skills/huiali-rust-skills-rust-error-advanced-e25443 && rm -rf "$T"
manifest:
skills/rust-error-advanced/SKILL.mdsource content
Result vs Option vs panic
| 类型 | 何时使用 | 示例 |
|---|---|---|
| 预期会失败的操作 | 文件读取、网络请求 |
| absence 正常 | 查找、可能为空的值 |
| bug 或不变式违规 | 程序逻辑错误、不可恢复错误 |
| 理论上不会执行到的代码 | 匹配穷举 |
错误处理决策
失败是预期的吗? │ ├─ 是 → 这是库代码? │ ├─ 是 → thiserror(类型化错误) │ └─ 否 → anyhow(易用性) │ ├─ 否,absence 正常? │ └─ Option<T> │ └─ 否,bug 或不变式违规 └─ panic!, assert!
thiserror(库代码)
use thiserror::Error; #[derive(Error, Debug)] pub enum MyError { #[error("validation failed: {0}")] Validation(String), #[error("IO error: {source}")] Io { #[from] source: std::io::Error, }, #[error("not found: {entity}:{id}")] NotFound { entity: String, id: u64, }, } // 使用 ? 传播 fn read_config() -> Result<Config, MyError> { let content = std::fs::read_to_string("config.toml")?; Ok(toml::from_str(&content)?) }
anyhow(应用代码)
use anyhow::{Context, Result, bail}; fn process_user(id: u64) -> Result<User> { let user = db.find_user(id) .with_context(|| format!("failed to find user {}", id))?; if !user.is_active { bail!("user {} is not active", id); } Ok(user) } // 组合多个错误源 fn complex_operation() -> Result<()> { let a = operation_a().context("operation A failed")?; let b = operation_b().context("operation B failed")?; Ok(()) }
错误设计原则
| 场景 | 建议 |
|---|---|
| 库代码 | thiserror,精确的错误类型 |
| 应用代码 | anyhow,易于传播和添加上下文 |
| 库依赖库 | 传递第三方错误() |
| 需要错误码 | 枚举变体 |
| 需要错误链 | + |
常见反模式
| 反模式 | 问题 | 解决 |
|---|---|---|
处处 | 库中 panic | 用 |
| 丢失类型信息 | thiserror 变体 |
| 丢失上下文 | 调试困难 | |
| 错误变体过多 | 过度设计 | 简化或合并 |
panic 使用场景
// 1. 不变量验证(公开 API) pub fn divide(a: f64, b: f64) -> f64 { if b == 0.0 { panic!("division by zero"); // 公开 API,确保调用者不传入 0 } a / b } // 2. 不可恢复错误 fn start_engine() { let config = load_critical_config(); if config.is_corrupted() { panic!("cannot start without valid config"); } } // 3. 匹配穷举(理论上的永远执行不到) fn process_status(status: Status) { match status { Status::Running => { /* ... */ } Status::Stopped => { /* ... */ } // 未来可能添加新状态 // _ => unreachable!("unknown status: {:?}", status), } } // 4. 内部不变量 assert!(!queue.is_empty(), "queue should never be empty here");
错误链
// 使用 map_err 转换错误 fn high_level() -> Result<()> { low_level() .map_err(|e| MyError::from_low_level(e, "high level operation failed")) } // 使用 with_context 添加调用链信息 fn middle_layer() -> Result<()> { low_level() .with_context(|| format!("while processing request {}", request_id))?; Ok(()) }
最佳实践
- 库代码:精确的错误类型(thiserror)
- 应用代码:易用性优先(anyhow)
- 传播错误:用
而非?unwrap() - 添加上下文:使用
或.context()with_context() - 保留错误源:用
保留底层错误#[from] - 区分 panic 场景:bug 用 panic,预期失败用 Result