7.3 ? 操作符简化错误传播

在函数中处理多个可能失败的操作时,频繁使用 matchand_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::openread_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 周前

内容由 AI 创作和分享,仅供参考