一句话总结
Rust 把错误分两类:可恢复错误用 Result,不可恢复错误用 panic!.
? 运算符让错误传播像 Go 的 if err != nil 一样直接却更简洁,还能借 From 自动转换错误类型.
生态里 thiserror / anyhow 进一步减负.
两类错误,两种机制
Go 只有一套错误观(error 接口 + panic).Rust 在类型层面分得更清楚:
| 错误性质 | 机制 | 典型场景 | Go 类比 |
|---|---|---|---|
| 可恢复(预期内) | Result<T, E> |
文件不存在,解析失败,网络超时 | 返回 error |
| 不可恢复(bug) | panic! |
数组越界,断言失败,不变量被破坏 | panic |
把可恢复错误想成"顾客点的菜没了"--正常业务,换一道即可.
不可恢复错误是"后厨着火了"--程序无法继续,只能紧急停业(panic).
Rust 用不同的类型强制区分对待,而 Go 全凭约定.
Result:可恢复错误的载体
Result 和 Option 一样是枚举(第 06 篇),只是把"无值"换成了"出错原因":
|
凡是可能失败的操作,返回类型就是 Result.调用方用 match 处理:
|
Go 的 file, err := os.Open(...) 把结果和错误拆成两个返回值;Rust 把它们合并进一个 Result 值.
区别在于:Go 可以无视 err 直接用 file(埋下隐患),Rust 必须先从 Result 里"取出" Ok 才能拿到 file,绕不过去.
? 运算符:优雅的错误传播
真实代码里,错误常需逐层上抛.Go 的写法是反复的 if err != nil { return err }:
|
Rust 的 ? 把这套样板压成一个符号:是 Ok(v) 就取出 v 继续;是 Err(e) 就立即 return 这个错误:
|
两个 ? 取代四行 if err != nil,逻辑主线清晰浮现,错误处理退到背景.
? 的使用前提
? 只能用在返回 Result 或 Option 的函数里(因为它要 return 错误).
在 main 中使用时,把 main 返回类型写成 Result<(), Box<dyn std::error::Error>>.这点和 Go "任何函数都能 return err" 略有不同--Rust 要求函数签名先"声明自己会失败".
? 与 From:自动转换错误类型
? 有个强大的隐藏能力,直接用上了第 13 篇的 From trait:当函数返回的错误类型与 ? 处产生的错误类型不同时,它会自动调用 From::from 进行转换.这让能把多种底层错误统一成一个上层错误类型:
|
这比 Go 的 fmt.Errorf("...: %w", err) 更自动--定义好 From,? 就替完成转换.
自定义错误类型与生态工具
上面手写 From 还是略繁琐.社区有两个事实标准大幅减负:
| 工具 | 定位 | 适用 |
|---|---|---|
thiserror |
用派生宏自动生成自定义错误类型(含 Display,From,Error 实现) | 库:需要明确的错误类型供调用方匹配 |
anyhow |
提供一个万能错误类型 anyhow::Error,任意错误都能塞进去 |
应用:只想方便地传播错误,不在乎精确类型 |
|
最佳实践:库用 thiserror,应用用 anyhow
经验法则:写库时用 thiserror 定义清晰的错误枚举,让调用者能精确匹配处理;写应用(二进制程序)时用 anyhow 图省事,直接 ? 传播各种错误到 main.
这对应 Go 里标准 errors 与 pkg/errors 的取舍.入门阶段先理解标准库的 Result/?,需要时再引入这两个 crate.
unwrap 与 expect:便利但危险
两个方法能"强行"从 Result/Option 取值:成功返回内部值,失败就 panic:
|
生产代码慎用 unwrap
unwrap 把可恢复错误升级成了崩溃.它适合原型,示例,测试,或能逻辑确证不会失败的场景.
生产业务路径上,应当用 ? 传播或 match 妥善处理.看到满屏 unwrap 的代码,基本可判定未认真处理错误.需要表达"这里不可能失败"时,优先用 expect("原因") 写明理由,方便日后排查.
panic:何时该用
panic! 用于程序无法合理继续的情形--通常意味着代码有 bug 或核心不变量被破坏:
|
判断准则:错误是"预期会发生,调用方应当处理"的(如用户输入非法)→ 用 Result;错误是"不该发生,发生即 bug"的 → 用 panic!.绝大多数业务错误属于前者.
快速回顾
- 两类错误:可恢复用
Result<T, E>,不可恢复(bug)用panic!. - Result:
Ok/Err枚举,把结果与错误合并进一个值,必须显式取出才能用. - ? 运算符:成功取值,失败即 return,取代成片
if err != nil;靠From自动转换错误类型. - 自定义错误:用枚举收拢失败原因;
thiserror(库)/anyhow(应用)大幅减负. - unwrap / expect:失败即 panic,仅用于原型或确证不失败处;表达"不可能失败"优先
expect写明理由. - 选择准则:预期内,应被处理 → Result;不该发生,属 bug → panic.
动手练习
- 自定义 Result:写
fn parse_age(s: &str) -> Result<u32, String>,解析失败时返回描述性错误. - ? 串联:写一个连续做两步可能失败操作的函数,用
?串联,让main返回Result<(), Box<dyn std::error::Error>>. - From 转换:定义
enum AppError并手写两个From实现,让两种底层错误都能被?自动转换. - thiserror 简化:引入
thiserror,用#[derive(Error)]+#[from]重写上一题,对比代码量. - unwrap vs expect:对一个会失败的
parse分别调用unwrap和expect("..."),对比 panic 信息的可读性.