Rust `?` 运算符:错误传播的快捷方式
1. ? 是什么
? 是 Rust 中传播错误的语法糖。它把两件事压缩成一个字符:
Ok(v)→ 取出v,继续执行Err(e)→ 从当前函数return Err(e.into()),不继续执行
1 | fn read_config() -> Result<String, std::io::Error> { |
一行 ? 替代了手写 match 的五行样板。
2. 等价展开:? → match
编译器对 ? 的机械展开如下:
1 | // expression? 展开为 ↓ |
关键点:
From::from(e)—— 调用Fromtrait 做错误类型转换。这意味着?不仅能传播错误,还能自动把一种错误类型转成另一种。return—— 直接退出当前函数,不是break,不是continue。
2.1 自动类型转换:From trait 的作用
1 | fn parse_and_read(path: &str) -> Result<String, Box<dyn std::error::Error>> { |
两种不同的错误类型(ParseIntError、io::Error)都能通过 ? 转成同一个 Box<dyn Error>,因为标准库为它们实现了 From<T> for Box<dyn Error>。
提示:Box<dyn Error> 是快速原型阶段的常用”万能错误类型”,正式项目推荐用 anyhow(应用层)或 thiserror(库层)替代。
2.2 与 unwrap 的区别
? |
unwrap |
|
|---|---|---|
| 行为 | 传播错误,不 panic | Err 时 panic |
| 返回类型要求 | 函数必须返回 Result/Option |
无要求 |
| 适用场景 | 可恢复错误,向上传递 | 逻辑上不可能出错处 |
1 | // unwrap:出错就崩 |
3. 错误传播链路
多层函数调用中,? 让错误自动向上冒泡,无需每层都 match:
1 | use std::fs; |
同一份错误会沿着调用栈逐层向上,每层只加一个 ?,无需 if/else 嵌套。
对比不用 ? 的等价写法:
1 | fn load_user(path: &str) -> Result<User, io::Error> { |
每层多 4 行,且错误类型不同时还需要手动 map_err。
4. 何时能用 ?
4.1 函数返回类型约束
? 只能在返回类型为 Result 或 Option 的函数中使用——编译器需要知道早期返回应该返回什么:
1 | // ✅ 返回 Result |
4.2 main 返回 Result
1 | fn main() -> Result<(), Box<dyn std::error::Error>> { |
main 返回 Result 时,Err 会被运行时打印并设置非零退出码。
4.3 Option 上的 ?
? 对 Option<T> 的行为对称:
1 | // opt? 展开为 ↓ |
示例——链式提取多层 Option:
1 | fn city_of(user: &User) -> Option<&str> { |
5. 常用模式
5.1 ? 与 map_err 配合
当错误类型不匹配且没有 From 实现时,用 map_err 手动转换:
1 | fn parse_config(raw: &str) -> Result<Config, ConfigError> { |
这里 Option? 和 Result? 混用在同一表达式链中——find 和 strip_prefix 返回 Option,parse 返回 Result。
5.2 闭包中的 ?:类型推断陷阱
闭包中使用 ?,编译器会推断闭包的返回类型:
1 | let items = vec!["1", "2", "x", "4"]; |
map 中对每个元素执行闭包,? 让闭包返回 Result<i32, _>。一旦遇到 "x" 解析失败,整个 map 链就停止。这种情况要么用 filter_map + .ok() 跳过失败项,要么在 map 外收集 Result<Vec<_>, _> 再 ?。
1 | // ✅ 收集整个 Result,一处 ? 即可 |
5.3 try 块(不稳定特性)
try { } 块允许在非函数出口的局部作用域中使用 ?,将错误捕获为外层 Result:
1 |
|
try 块内的 ? 退出的是 try 块本身,而非外层函数。
6. 小结
? 的核心价值是消除错误传播的样板代码:
| 机制 | 作用 |
|---|---|
expr? |
Ok(v) → v,Err(e) → return Err(e.into()) |
From::from |
自动转换错误类型,无需手动 map_err |
| 类型约束 | 函数必须返回 Result 或 Option |
main 支持 |
main() → Result<(), E> 后可在入口函数使用 ? |
理解 ? = 理解那一段固定的 match 展开。此后所有用法都是这个展开的组合。
