Rust 错误处理
可恢复错误用 Result,不可恢复用 panic;? 传播、错误类型设计与库/二进制边界策略。
#type / concept
#status / growing
#tech / dev
#resource / rust
[!info] 关联笔记
Rust 错误处理
这个概念为什么出现
失败分两类:
- 可恢复:文件不存在、校验失败、下游超时 → 调用方可能换路径
- 不可恢复:程序不变量被破坏 → 立刻停止比带病运行更安全
Rust 用 Result 服务第一类,用 panic 服务第二类。
[!abstract] 一句话理解 可恢复错误返回
Result并用?传播;panic留给 bug/不变量;库暴露类型化错误,二进制在边界决定日志与退出码。
最小可运行示例
场景:配置加载——缺文件可恢复,解析到非法端口则失败
服务启动读端口配置;缺文件给默认;端口非数字则返回错误,由 main 打印并退出语义。
use std::fs;
use std::num::ParseIntError;
// 业务意图:读取端口;缺省 8080;坏数据向上返回。
// 教学点:Result 传播;map_err 增加上下文;main 返回 Result。
fn read_port(path: &str) -> Result<u16, String> {
let text = match fs::read_to_string(path) {
Ok(t) => t,
Err(_) => return Ok(8080), // 缺文件:可恢复默认
};
text.trim()
.parse::<u16>()
.map_err(|e: ParseIntError| format!("invalid port '{text}': {e}"))
}
fn main() -> Result<(), String> {
// 演示:用内联解析代替真实文件,保证示例可运行
let port: u16 = "3000".parse().map_err(|e| format!("{e}"))?;
println!("listening_port={port}");
let bad = read_port("/this/path/should/not/exist/app.port")?;
println!("default_or_file_port={bad}");
Ok(())
}
建议运行:cargo run
期望输出(示例):
listening_port=3000
default_or_file_port=8080
结合场景再看三个关注点
- 缺文件有默认是产品决策,用
Result分支表达 ?在main可简洁传播- 上下文用
map_err/anyhow上下文(生态)
核心概念与准确模型
Result 传播
let v = fallible()?; // 等价于 match 后 early return
? 需要返回类型兼容(From 转换错误类型)。
panic
panic!/unwrap/ 数组越界等- 用于“程序员错误”
- 可
catch_unwind但不是常规控制流
错误类型策略
| 场景 | 常见选择 |
|---|---|
| 库公共 API | 自定义错误枚举 / thiserror |
| 应用二进制 | anyhow::Error 等易用包装 |
| 细粒度匹配 | 枚举变体 + source 链 |
设计动机
- 失败可见
- 零成本抽象下的显式控制流
- 区分 bug 与业务失败
边界与误区
- 不要用 panic 处理用户输入错误
- 不要在库里到处
unwrap - 错误类型过大导致匹配痛苦
[!warning] 常见误区:所有失败都 panic 用户输入、网络抖动必须可恢复。
工程实践
- 库:类型化错误 +
Display/Errortrait - 应用:边界记录日志、指标、退出码
- 保留
source链便于排障 - 测试断言错误变体而不只是
is_err
本节总结
- Result vs panic
?传播- 库/应用错误策略分层
自测题
- 用户输入非法邮箱该 Result 还是 panic?
- 为什么库不宜只返回
String错误?
参考答案
- Result。
- 调用方难以稳定匹配与包装,也难做 i18n/结构化。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — Error Handling | 官方书 | 错误处理 |
| std::error::Error | 标准库 | Error trait |
| anyhow / thiserror | 生态 | 应用/库错误 |