错误处理与 Panic 恢复
📚 Rust 课程系列
Rust 把错误分成两类:可恢复错误用 Result<T, E> 表达,交由调用方决定如何处理;不可恢复错误(违反不变式、无法继续)用 panic! 终止当前线程。本章先讲 Option/Result/? 与组合子,再讲 panic 的行为、捕获与断言,帮你建立「何时该 panic、何时该 Result」的判断。
错误处理
Rust 没有 try/catch,而是用类型表达「可能失败」:
| 类型 | 含义 | 变体 |
|---|---|---|
Option<T> | 可能为空 | Some(T) / None |
Result<T, E> | 可能出错 | Ok(T) / Err(E) |
🔄 对比:Rust 的
Result≈ Go 的(value, error)多返回值,但编译器通过?强制你处理错误,而非靠约定检查err != nil。
1 | |
? 运算符与错误传播
? 的语义:成功则解包继续,失败则提前 return Err(...)。它还会通过 From trait 自动把底层错误转换成当前函数的返回错误类型,因此不同来源的错误可以统一汇聚到一个 Error 枚举:
1 | |
💡 提示:
?只能用在返回Result/Option的函数中。在main里使用需要main返回Result<(), E>(Rust 2018+ 支持)。
⚠️ 注意:
?会静默转换错误类型。如果From实现有误,可能把关键错误信息吞掉——确保From实现保留原始错误链(推荐用thiserror的#[from]自动生成)。
库 vs 应用的错误类型策略
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 库(library) | 精确 enum Error + thiserror | 让调用方能按分支处理 |
| 应用(binary) | anyhow::Error 或 eyre::Report | 只关心「失败就报错退出」,省事 |
thiserror 用 #[derive(Error)] 简化自定义错误枚举的 Display/From 实现;anyhow 提供 .context() 方法附带额外上下文,便于排查问题。
💡 提示:需要附带上下文时,用
map_err或anyhow的.with_context(|| ...)而非裸?,这样错误信息才有定位价值。
组合子方法
组合子让你像流水线一样处理 Option/Result,避免层层 match:
1 | |
常用组合子速查:
| 方法 | 作用 | 适用 |
|---|---|---|
map | 转换成功值 | Option/Result |
map_err | 转换错误值 | Result |
and_then | 链式操作(flatMap) | Option/Result |
unwrap_or | 失败时提供默认值 | Option/Result |
unwrap_or_else | 失败时用闭包计算默认值 | Option/Result |
unwrap_or_default | 失败时用 Default | Option/Result |
何时 panic、何时 Result
经验法则:
- 调用方应当能合理处理的失败 ->
Result - 不变量被破坏、继续执行会破坏数据安全 ->
panic!(如unwrap一个「按约定一定有值」的Option) - 库内部的失败应尽量用
Result,不要替调用方决定「致命」
💡 提示:一个简单的判断——「如果调用方看到这个错误,能做有意义的事吗?」能 ->
Result;不能 ->panic。
Panic 与错误恢复
panic 表示「发生了无法恢复的编程错误」,默认行为是展开栈(unwinding)——逐层调用 Drop 清理资源后线程退出。
🔄 对比:Rust 的 panic ≈ C++ 的
std::terminate/ Go 的panic,但 Rust 默认只终止当前线程而非整个进程(除非panic = "abort")。
unwrap / expect 系列
1 | |
⚠️ 注意:
unwrap/expect适合「按约定一定成功」的场景(如刚insert过就立刻get的HashMap)以及测试/原型代码。生产代码里对可能失败的输入应改用?或unwrap_or系列。
Panic 时中止(进阶)
1 | |
1 | |
⚠️ 注意:
panic = "abort"跳过栈展开直接终止进程,可减小二进制体积(无需展开表),但代价是Drop不会被调用、也无法被catch_unwind捕获。
🔬 进阶:嵌入式和 WASM 目标默认
panic = "abort",因为展开表占用空间且目标平台可能不支持展开。
捕获 Panic
1 | |
⚠️ 注意:
catch_unwind只能捕获展开(unwinding)的 panic,不能捕获panic = "abort"或abort发出的 panic。
catch_unwind 主要用于 FFI 边界(C 调用 Rust 时不能让 panic 跨越边界)或在「希望一个子任务失败不影响整体」时隔离故障。不应当作常规错误处理手段——能用 Result 就用 Result。
🔬 进阶:跨 FFI 边界的 panic 是 UB(未定义行为)。如果你的库可能被 C 代码调用,在
Cargo.toml中加panic = "unwind"并在 FFI 函数入口用catch_unwind包裹,或使用#[unwind(aborts)](nightly)。
Assert 宏
断言失败会 panic,常用于测试与不变式检查:
1 | |
💡 提示:
debug_assert!在 release 构建中被移除,适合「昂贵的、仅用于调试期发现逻辑错误」的检查;正式不变式应使用assert!,确保始终生效。
🔄 对比:
debug_assert!≈ C/C++ 的assert()宏(NDEBUG时移除),但 Rust 的assert!始终生效,不像 C 的assert在 release 中默认消失。


