7.3 ? 操作符简化错误传播
听
在函数中处理多个可能失败的操作时,频繁使用 match 或 and_then 会导致代码冗长。Rust 提供了 ? 操作符,用于自动传播错误:当表达式返回 Ok(value) 时,? 提取 value;若返回 Err(e),则立即从当前函数返回 Err(e)。
基本用法
考虑一个需要打开文件并读取内容的函数:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("username.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
这里,两个 ? 分别作用于 File::open 和 read_to_string 的返回值(均为 Result)。如果任一操作失败,函数会提前返回对应的 Err;否则继续执行。
上述代码等价于:
fn read_username_from_file() -> Result<String, io::Error> {
match File::open("username.txt") {
Ok(f) => {
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
Err(e) => Err(e),
}
}
显然,? 大幅简化了错误传播逻辑。
返回类型要求
使用 ? 的函数必须返回 Result(或实现了 FromResidual 的类型),且错误类型需兼容。例如,若 ? 遇到 Err(io::Error),则函数返回类型中的错误部分必须能接收 io::Error。
若函数返回 Result<T, Box<dyn std::error::Error>>,由于 Box<dyn Error> 可通过 From 转换接受大多数标准错误,? 也能正常工作。
在 main 或测试函数中使用 ?
main 函数通常不返回 Result,但 Rust 允许其返回 Result<(), E>,以便直接使用 ?:
use std::fs::File;
fn main() -> Result<(), std::io::Error> {
let f = File::open("config.txt")?;
// ...
Ok(())
}
类似地,测试函数也可返回 Result:
#[test]
fn test_something() -> Result<(), Box<dyn std::error::Error>> {
let data = std::fs::read_to_string("test_data.json")?;
assert!(!data.is_empty());
Ok(())
}
? 与 Option
? 不仅适用于 Result,也适用于 Option。在返回 Option 的函数中,some_val? 会在 None 时提前返回 None,否则解包为内部值。
fn first_even(v: Vec<i32>) -> Option<i32> {
for x in v {
if x % 2 == 0 {
return Some(x);
}
}
None
}
fn double_first_even(v: Vec<i32>) -> Option<i32> {
let n = first_even(v)?; // 若为 None,提前返回 None
Some(n * 2)
}
注意事项
?不是“忽略错误”,而是将错误向上传播;- 过度使用
?可能使错误处理逻辑不清晰,应在适当层级捕获并处理错误; - 错误类型不一致时,需通过
map_err或自定义错误转换来统一。
小结
? 操作符是 Rust 错误处理的重要语法糖,它在保持显式错误传播的同时,显著减少了样板代码。合理使用 ?,可以让数据处理、I/O 操作等链式流程更加简洁流畅,同时不牺牲安全性。它是连接底层操作与高层逻辑的桥梁,也是现代 Rust 代码中几乎无处不在的惯用法。
#Rust 入门教程
分享于 1 周前
上一篇:7.2 使用 Result 进行可恢复错误处理
下一篇:7.4 自定义错误类型