09-错误处理

panic 与 Result、? 传播及何时 panic 的决策要点

精要笔记 · 基于 TRPL 简体中文 第9章

错误处理

Rust 无异常;分两类:

类型机制场景
不可恢复panic!bug、违反契约
可恢复Result<T, E>文件不存在等可处理失败

用 panic! 处理不可恢复的错误

  • 触发方式:显式 panic!;或越界访问等。
  • 默认:展开(unwind)栈并清理;可设 panic = 'abort' 直接终止(release 减小体积)。
1
2
[profile.release]
panic = 'abort'
1
panic!("crash and burn");

使用 panic! 的 backtrace

1
RUST_BACKTRACE=1 cargo run
  • 从 backtrace 最上往下找自己项目的第一个帧。

用 Result 处理可恢复的错误

1
2
3
4
enum Result<T, E> {
    Ok(T),
    Err(E),
}
1
let f = File::open("hello.txt");  // Result<File, io::Error>

匹配不同的错误

1
2
3
4
5
6
7
let f = File::open("hello.txt").unwrap_or_else(|error| {
    if error.kind() == ErrorKind::NotFound {
        File::create("hello.txt").unwrap_or_else(|e| panic!("create: {e:?}"))
    } else {
        panic!("open: {error:?}")
    }
});

失败时 panic 的快捷方式

  • unwrap():Ok 取值,Err 则 panic。
  • expect("msg"):同上,可自定义 panic 信息(生产更推荐)。

传播错误

  • 函数返回 Result,用 return Err(e) 或 ? 向上传播。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

// 更简
fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}
  • ?:Ok 解包继续;Err 经 From 转换后提前 return。
  • 限制:只能在返回 Result / Option / 实现 FromResidual 的函数中用。
  • main 可返回 Result<(), Box<dyn Error>>,末尾 Ok(());Err 退出码非 0。

哪里可以使用 ? 运算符

  • Result 上 ? ↔ 返回 Result 的函数;Option 同理;不可混用(需 ok / ok_or 转换)。

要不要 panic!

用 Result(默认)用 panic!
调用者决定如何处理不可恢复 / 有害状态
预期可能失败(解析、HTTP 限流)违反函数契约、无效前置条件
库 API 暴露错误示例、原型、unwrap 占位

示例、代码原型和测试

  • 原型/测试:unwrap / expect 可接受;测试失败即 panic。

当你比编译器知道更多时

  • 逻辑上不可能 Err 时用 expect("原因"),并在消息中说明假设。

错误处理指导原则

  • 有害状态(不变量被破坏)且无法编码进类型 → panic。
  • 用类型系统减少运行时检查(非 Option 即必有值;u32 非负等)。

为验证创建自定义类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pub struct Guess { value: i32 }

impl Guess {
    pub fn new(value: i32) -> Self {
        if !(1..=100).contains(&value) {
            panic!("Guess must be 1..=100, got {value}");
        }
        Guess { value }
    }
    pub fn value(&self) -> i32 { self.value }
}
  • 私有字段 + new 构造:保证有效范围;API 文档应说明 panic 条件。
最后修改 July 30, 2026: 更新 (33710aaae)