这个问号运算符是什么意思?

268

我正在阅读关于文件的文档

//..
let mut file = File::create("foo.txt")?;
//..

这行代码中的 ? 是什么意思?我在 Rust 书中没有看到过。

2
请注意,?的描述已经包含在2018年的书籍中。https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html - Patrik Stas
4个回答

342
作为你可能已经注意到的,Rust 没有异常。它有 panic,但是不鼓励将其用于错误处理(它们用于不可恢复的错误)。
在 Rust 中,错误处理使用 Result。一个典型的例子如下:
fn halves_if_even(i: i32) -> Result<i32, Error> {
    if i % 2 == 0 {
        Ok(i / 2)
    } else {
        Err(/* something */)
    }
}

fn do_the_thing(i: i32) -> Result<i32, Error> {
    let i = match halves_if_even(i) {
        Ok(i) => i,
        Err(e) => return Err(e),
    };

    // use `i`
}

这很好,因为:
  • 在编写代码时,您不会意外忘记处理错误,
  • 在阅读代码时,您可以立即看到此处存在潜在错误。
然而,它过于冗长。这就是问号运算符 ? 登场的地方。
上述内容可重写为:
fn do_the_thing(i: i32) -> Result<i32, Error> {
    let i = halves_if_even(i)?;

    // use `i`
}

这里的?与上面的match语句等效,但增加了一个功能。简而言之:

  1. 如果是OK,则解包Result
  2. 如果不是,则返回错误,并在错误值上调用From::from以潜在地将其转换为另一种类型。

它有点神奇,但错误处理需要一些魔法来减少样板代码,与异常不同,可以立即看到哪些函数调用可能会出错:那些带有?的函数。

魔法的一个例子是,这也适用于Option

// Assume
// fn halves_if_even(i: i32) -> Option<i32>

fn do_the_thing(i: i32) -> Option<i32> {
    let i = halves_if_even(i)?;

    // use `i`
}
? 运算符在 Rust 1.13.0 版本中得到稳定,由(不稳定的)Try trait 支持。
另请参阅:

8
如果您能稍微扩展一下您的回答,例如讨论函数的返回类型必须与您尝试“解包”的类型匹配,例如 ResultOption,那将很好。 - hellow
@你好,我猜最好还是另起一问。 - Paul Razvan Berg
关于 panic 携带结构化信息,现在可以通过 panic_any 来实现。 - Lonami
@Lonami:已经修改。 - Matthieu M.
在Golang中有这样的东西会很不错。 - Yoh0xFF

26

它是一个后缀运算符,用于解包Result<T, E>Option<T>值。

如果应用于Result<T, E>,它将解包结果并给出内部值,将错误传播到调用函数。

let number = "42".parse::<i32>()?;
println!("{:?}", number); // 42

当应用于一个Option<T>时,它将None传播给调用者,留下了Some分支的内容让你处理。
let val = Some(42)?;
println!("{:?}", val); // 42

?运算符只能在返回ResultOption的函数中使用,用法如下:

use std::num::ParseIntError;

fn main() -> Result<(), ParseIntError> {
    let number = "42".parse::<i32>()?;
    println!("{:?}", number);
    Ok(())
}

Rust提供的便利功能可消除样板代码,使函数实现更简单。


20
不要混淆它与实际的 .unwrap(),后者在出现错误时会引发 panic。 - Jordan

10

它用于 传播错误。有时,我们编写的代码可能会失败,但我们不想立即捕获和处理错误。如果您在每个地方都要处理错误,那么您的代码将变得难以阅读。相反,如果发生错误,我们可能希望让调用者来处理它。我们希望错误向上一级调用堆栈传播。

 // file type is Result if "?" is not used
 // file:Result<File,Error>
 let mut file = File::create("foo.txt");

 // file type is File if "?" is used
 // file:File
 let mut file = File::create("foo.txt")?;
 // if an error occurs, code after this line will not be executed
 // function will return the error
?的行为取决于此函数返回的是成功结果还是错误结果:
  • 如果成功,它会解包Result以获取其中的成功值。将该值分配给变量file
  • 如果结果是错误,则不会将错误赋给变量file。错误将由函数返回给调用者。
使用?与此代码相同:
let mut file = match File::create("foo.txt") {
        Err(why) => panic!("couldn't create {}: {}", display, why),
        Ok(file) => file,
    };

?在Option类型中也有类似的作用。在返回Option的函数中,你可以使用?来解包一个值,并在None的情况下提前返回:


1

现有的答案都很好!我想提供一个小的代码片段来演示在这个问号后面使用From::from()的用法:

fn _parse(str: &str) -> Result<i32, &str> {
    if let Ok(num) = str.parse::<i32>() {
        Ok(num)
    } else {
        Err(str)
    }
}

fn parse(str: &str) -> Result<(), String> {
    let num = _parse(str)?;
    println!("{}", num);
    Ok(())
}

在函数parse()中,使用?可以手动重写为:
fn parse(str: &str) -> Result<(), String> {
    match _parse(str) {
        Ok(n) => {
            println!("{}", n);
            Ok(())
        }
        Err(str) => Err(<String as From<&str>>::from(str)),
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接