Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


x-i18n: generated_at: “2026-03-01T14:03:26Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: dab3e88d324cdde74cf96356c58a423747a5735ac6848d8d4592dc7c13637c62 source_path: ch09-02-recoverable-errors-with-result.md workflow: 16

使用 Result 处理可恢复的错误 (Recoverable Errors with Result)

Recoverable Errors with Result

大多数错误并不严重到需要程序完全停止。有时当一个函数失败时,是因为一个你可以轻松解释并做出反应的原因。例如,如果你尝试打开一个文件而该操作因为文件不存在而失败,你可能想创建该文件而不是终止进程。

Most errors aren’t serious enough to require the program to stop entirely. Sometimes when a function fails, it’s for a reason that you can easily interpret and respond to. For example, if you try to open a file and that operation fails because the file doesn’t exist, you might want to create the file instead of terminating the process.

回想第 2 章“使用 Result 处理潜在的失败”中,Result 枚举被定义为具有两个变体,OkErr,如下所示:

Recall from “Handling Potential Failure with Result in Chapter 2 that the Result enum is defined as having two variants, Ok and Err, as follows:

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

TE 是泛型类型参数:我们将在第 10 章更详细地讨论泛型。你现在需要知道的是,T 代表在 Ok 变体中成功情况下将返回的值的类型,而 E 代表在 Err 变体中失败情况下将返回的错误的类型。因为 Result 具有这些泛型类型参数,所以我们可以在许多不同的情况下使用 Result 类型及其定义的函数,其中我们要返回的成功值和错误值可能各不相同。

The T and E are generic type parameters: We’ll discuss generics in more detail in Chapter 10. What you need to know right now is that T represents the type of the value that will be returned in a success case within the Ok variant, and E represents the type of the error that will be returned in a failure case within the Err variant. Because Result has these generic type parameters, we can use the Result type and the functions defined on it in many different situations where the success value and error value we want to return may differ.

让我们调用一个返回 Result 值的函数,因为该函数可能会失败。在示例 9-3 中,我们尝试打开一个文件。

Let’s call a function that returns a Result value because the function could fail. In Listing 9-3, we try to open a file.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-03/src/main.rs}}
}

File::open 的返回类型是 Result<T, E>。泛型参数 T 已被 File::open 的实现填充为成功值的类型 std::fs::File,这是一个文件句柄。错误值中使用的 E 的类型是 std::io::Error。这种返回类型意味着调用 File::open 可能会成功并返回一个我们可以读取或写入的文件句柄。函数调用也可能会失败:例如,文件可能不存在,或者我们可能没有访问该文件的权限。File::open 函数需要有一种方式告诉我们它是成功还是失败,同时还要给我们文件句柄或错误信息。这些信息正是 Result 枚举所传达的。

The return type of File::open is a Result<T, E>. The generic parameter T has been filled in by the implementation of File::open with the type of the success value, std::fs::File, which is a file handle. The type of E used in the error value is std::io::Error. This return type means the call to File::open might succeed and return a file handle that we can read from or write to. The function call also might fail: For example, the file might not exist, or we might not have permission to access the file. The File::open function needs to have a way to tell us whether it succeeded or failed and at the same time give us either the file handle or error information. This information is exactly what the Result enum conveys.

File::open 成功的情况下,变量 greeting_file_result 中的值将是一个包含文件句柄的 Ok 实例。在失败的情况下,greeting_file_result 中的值将是一个包含有关所发生错误类型的更多信息的 Err 实例。

In the case where File::open succeeds, the value in the variable greeting_file_result will be an instance of Ok that contains a file handle. In the case where it fails, the value in greeting_file_result will be an instance of Err that contains more information about the kind of error that occurred.

我们需要在示例 9-3 的代码中添加内容,以便根据 File::open 返回的值采取不同的操作。示例 9-4 展示了使用我们在第 6 章中讨论过的基本工具 match 表达式来处理 Result 的一种方法。

We need to add to the code in Listing 9-3 to take different actions depending on the value File::open returns. Listing 9-4 shows one way to handle the Result using a basic tool, the match expression that we discussed in Chapter 6.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-04/src/main.rs}}
}

注意,就像 Option 枚举一样,Result 枚举及其变体已经由 prelude 引入作用域,所以我们不需要在 match 分支中的 OkErr 变体之前指定 Result::

Note that, like the Option enum, the Result enum and its variants have been brought into scope by the prelude, so we don’t need to specify Result:: before the Ok and Err variants in the match arms.

当结果为 Ok 时,这段代码将从 Ok 变体中返回内部的 file 值,然后我们将该文件句柄值分配给变量 greeting_file。在 match 之后,我们可以使用该文件句柄进行读取或写入。

When the result is Ok, this code will return the inner file value out of the Ok variant, and we then assign that file handle value to the variable greeting_file. After the match, we can use the file handle for reading or writing.

match 的另一个分支处理从 File::open 获得 Err 值的情况。在这个例子中,我们选择了调用 panic! 宏。如果我们的当前目录中没有名为 hello.txt 的文件,并且我们运行这段代码,我们将看到来自 panic! 宏的以下输出:

The other arm of the match handles the case where we get an Err value from File::open. In this example, we’ve chosen to call the panic! macro. If there’s no file named hello.txt in our current directory and we run this code, we’ll see the following output from the panic! macro:

{{#include ../listings/ch09-error-handling/listing-09-04/output.txt}}

照例,这个输出准确地告诉了我们哪里出了问题。

As usual, this output tells us exactly what has gone wrong.

匹配不同的错误 (Matching on Different Errors)

Matching on Different Errors

示例 9-4 中的代码无论 File::open 失败的原因是什么都会 panic!。然而,我们希望针对不同的失败原因采取不同的行动。如果 File::open 因为文件不存在而失败,我们希望创建该文件并返回新文件的句柄。如果 File::open 因为任何其他原因失败(例如,因为我们没有打开文件的权限),我们仍然希望代码以示例 9-4 中的方式 panic!。为此,我们添加了一个嵌套的 match 表达式,如示例 9-5 所示。

The code in Listing 9-4 will panic! no matter why File::open failed. However, we want to take different actions for different failure reasons. If File::open failed because the file doesn’t exist, we want to create the file and return the handle to the new file. If File::open failed for any other reason—for example, because we didn’t have permission to open the file—we still want the code to panic! in the same way it did in Listing 9-4. For this, we add an inner match expression, shown in Listing 9-5.

{{#rustdoc_include ../listings/ch09-error-handling/listing-09-05/src/main.rs}}

File::openErr 变体内部返回的值的类型是 io::Error,这是一个由标准库提供的结构体。该结构体有一个 kind 方法,我们可以调用它来获得一个 io::ErrorKind 值。枚举 io::ErrorKind 由标准库提供,其中的变体代表了 io 操作可能产生的不同种类的错误。我们想要使用的是 ErrorKind::NotFound 变体,它表示我们尝试打开的文件尚不存在。因此,我们对 greeting_file_result 进行匹配,但在 Err 分支内还对 error.kind() 进行了一个嵌套匹配。

The type of the value that File::open returns inside the Err variant is io::Error, which is a struct provided by the standard library. This struct has a method, kind, that we can call to get an io::ErrorKind value. The enum io::ErrorKind is provided by the standard library and has variants representing the different kinds of errors that might result from an io operation. The variant we want to use is ErrorKind::NotFound, which indicates the file we’re trying to open doesn’t exist yet. So, we match on greeting_file_result, but we also have an inner match on error.kind().

我们在嵌套匹配中想要检查的条件是 error.kind() 返回的值是否是 ErrorKind 枚举的 NotFound 变体。如果是,我们尝试使用 File::create 创建该文件。然而,因为 File::create 也可能会失败,所以我们在嵌套的 match 表达式中需要第二个分支。当文件无法创建时,会打印一条不同的错误消息。外层 match 的第二个分支保持不变,因此除了文件缺失错误之外的任何错误都会导致程序恐慌。

The condition we want to check in the inner match is whether the value returned by error.kind() is the NotFound variant of the ErrorKind enum. If it is, we try to create the file with File::create. However, because File::create could also fail, we need a second arm in the inner match expression. When the file can’t be created, a different error message is printed. The second arm of the outer match stays the same, so the program panics on any error besides the missing file error.

Result<T, E> 中使用 match 的替代方案

这里的 match 真多!match 表达式非常有用,但也是非常基础的。在第 13 章中,你将学习闭包(closures),它们与 Result<T, E> 上定义的许多方法配合使用。当在代码中处理 Result<T, E> 值时,这些方法可以比使用 match 更简洁。

例如,这里有另一种编写与示例 9-5 相同逻辑的方式,这次使用了闭包和 unwrap_or_else 方法:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {error:?}");
            })
        } else {
            panic!("Problem opening the file: {error:?}");
        }
    });
}

虽然这段代码的行为与示例 9-5 相同,但它不包含任何 match 表达式,而且读起来更清爽。在阅读完第 13 章后回到这个例子,并在标准库文档中查找 unwrap_or_else 方法。在处理错误时,还有更多此类方法可以清理庞大、嵌套的 match 表达式。

Alternatives to Using match with Result<T, E>

That’s a lot of match! The match expression is very useful but also very much a primitive. In Chapter 13, you’ll learn about closures, which are used with many of the methods defined on Result<T, E>. These methods can be more concise than using match when handling Result<T, E> values in your code.

For example, here’s another way to write the same logic as shown in Listing 9-5, this time using closures and the unwrap_or_else method:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {error:?}");
            })
        } else {
            panic!("Problem opening the file: {error:?}");
        }
    });
}

Although this code has the same behavior as Listing 9-5, it doesn’t contain any match expressions and is cleaner to read. Come back to this example after you’ve read Chapter 13 and look up the unwrap_or_else method in the standard library documentation. Many more of these methods can clean up huge, nested match expressions when you’re dealing with errors.

错误时引发恐慌的简写 (Shortcuts for Panic on Error)

Shortcuts for Panic on Error

使用 match 效果还不错,但可能有点冗长,而且并不总能很好地传达意图。Result<T, E> 类型定义了许多辅助方法来执行各种更具体的任务。unwrap 方法是一个简写方法,其实现就像我们在示例 9-4 中编写的 match 表达式一样。如果 Result 值是 Ok 变体,unwrap 将返回 Ok 内部的值。如果 ResultErr 变体,unwrap 将为我们调用 panic! 宏。下面是 unwrap 实际运行的一个例子:

Using match works well enough, but it can be a bit verbose and doesn’t always communicate intent well. The Result<T, E> type has many helper methods defined on it to do various, more specific tasks. The unwrap method is a shortcut method implemented just like the match expression we wrote in Listing 9-4. If the Result value is the Ok variant, unwrap will return the value inside the Ok. If the Result is the Err variant, unwrap will call the panic! macro for us. Here is an example of unwrap in action:

文件名: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-04-unwrap/src/main.rs}}
}

如果我们运行这段代码且没有 hello.txt 文件,我们将看到来自 unwrap 方法所执行的 panic! 调用的错误消息:

If we run this code without a hello.txt file, we’ll see an error message from the panic! call that the unwrap method makes:

thread 'main' panicked at src/main.rs:4:49:
called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }

类似地,expect 方法还允许我们选择 panic! 错误消息。使用 expect 而不是 unwrap 并提供良好的错误消息可以传达你的意图,并使追踪恐慌源头变得更容易。expect 的语法看起来像这样:

Similarly, the expect method lets us also choose the panic! error message. Using expect instead of unwrap and providing good error messages can convey your intent and make tracking down the source of a panic easier. The syntax of expect looks like this:

文件名: src/main.rs

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-05-expect/src/main.rs}}
}

我们以与 unwrap 相同的方式使用 expect:返回文件句柄或调用 panic! 宏。expect 在其 panic! 调用中使用的错误消息将是我们传递给 expect 的参数,而不是 unwrap 使用的默认 panic! 消息。它看起来像这样:

We use expect in the same way as unwrap: to return the file handle or call the panic! macro. The error message used by expect in its call to panic! will be the parameter that we pass to expect, rather than the default panic! message that unwrap uses. Here’s what it looks like:

thread 'main' panicked at src/main.rs:5:10:
hello.txt should be included in this project: Os { code: 2, kind: NotFound, message: "No such file or directory" }

在具有生产质量的代码中,大多数 Rustaceans 会选择 expect 而不是 unwrap,并提供更多关于操作为何预计总是成功的上下文。这样,如果你的假设被证明是错误的,你就有了更多可用于调试的信息。

In production-quality code, most Rustaceans choose expect rather than unwrap and give more context about why the operation is expected to always succeed. That way, if your assumptions are ever proven wrong, you have more information to use in debugging.

传播错误 (Propagating Errors)

Propagating Errors

当函数的实现调用了可能会失败的操作时,你可以在函数内部不处理错误,而是将错误返回给调用代码,以便由其决定如何操作。这被称为“传播 (propagating)”错误,它赋予了调用代码更多的控制权,因为调用代码可能拥有比你代码上下文更多的信息或逻辑来决定应该如何处理错误。

When a function’s implementation calls something that might fail, instead of handling the error within the function itself, you can return the error to the calling code so that it can decide what to do. This is known as propagating the error and gives more control to the calling code, where there might be more information or logic that dictates how the error should be handled than what you have available in the context of your code.

例如,示例 9-6 显示了一个从文件中读取用户名的函数。如果文件不存在或无法读取,此函数会将这些错误返回给调用该函数的代码。

For example, Listing 9-6 shows a function that reads a username from a file. If the file doesn’t exist or can’t be read, this function will return those errors to the code that called the function.

#![allow(unused)]
fn main() {
{{#include ../listings/ch09-error-handling/listing-09-06/src/main.rs:here}}
}

这个函数可以写得简短得多,但为了探索错误处理,我们将先通过大量手动操作开始;最后,我们将展示简短的方法。让我们先看看函数的返回类型:Result<String, io::Error>。这意味着该函数返回一个 Result<T, E> 类型的值,其中泛型参数 T 已填充为具体类型 String,泛型类型 E 已填充为具体类型 io::Error

This function can be written in a much shorter way, but we’re going to start by doing a lot of it manually in order to explore error handling; at the end, we’ll show the shorter way. Let’s look at the return type of the function first: Result<String, io::Error>. This means the function is returning a value of the type Result<T, E>, where the generic parameter T has been filled in with the concrete type String and the generic type E has been filled in with the concrete type io::Error.

如果此函数运行顺利没有任何问题,调用此函数的代码将接收到一个包含 StringOk 值——即此函数从文件中读取的 username。如果此函数遇到任何问题,调用代码将接收到一个包含 io::Error 实例的 Err 值,该实例包含有关问题的更多信息。我们选择 io::Error 作为此函数的返回类型,是因为在此函数体中我们调用的可能失败的两个操作恰好都返回此类型的错误值:File::open 函数和 read_to_string 方法。

If this function succeeds without any problems, the code that calls this function will receive an Ok value that holds a String—the username that this function read from the file. If this function encounters any problems, the calling code will receive an Err value that holds an instance of io::Error that contains more information about what the problems were. We chose io::Error as the return type of this function because that happens to be the type of the error value returned from both of the operations we’re calling in this function’s body that might fail: the File::open function and the read_to_string method.

函数体从调用 File::open 函数开始。然后,我们使用类似于示例 9-4 中 match 的方式来处理 Result 值。如果 File::open 成功,模式变量 file 中的文件句柄就成为了可变变量 username_file 中的值,函数继续执行。在 Err 的情况下,我们不调用 panic!,而是使用 return 关键字从整个函数提前返回,并将 File::open 的错误值(现在在模式变量 e 中)传回给调用代码作为此函数的错误值。

The body of the function starts by calling the File::open function. Then, we handle the Result value with a match similar to the match in Listing 9-4. If File::open succeeds, the file handle in the pattern variable file becomes the value in the mutable variable username_file and the function continues. In the Err case, instead of calling panic!, we use the return keyword to return early out of the function entirely and pass the error value from File::open, now in the pattern variable e, back to the calling code as this function’s error value.

因此,如果我们 username_file 中有一个文件句柄,函数随后会在变量 username 中创建一个新 String,并调用 username_file 中文件句柄的 read_to_string 方法,将文件内容读取到 username 中。read_to_string 方法也会返回一个 Result,因为它可能会失败,即使 File::open 成功了。所以,我们需要另一个 match 来处理该 Result:如果 read_to_string 成功,那么我们的函数就成功了,我们返回文件中现在位于 username 中的用户名,并将其包裹在 Ok 中。如果 read_to_string 失败,我们返回错误值的方式与处理 File::open 返回值的 match 中返回错误值的方式相同。然而,我们不需要显式地说 return,因为这是函数的最后一个表达式。

So, if we have a file handle in username_file, the function then creates a new String in variable username and calls the read_to_string method on the file handle in username_file to read the contents of the file into username. The read_to_string method also returns a Result because it might fail, even though File::open succeeded. So, we need another match to handle that Result: If read_to_string succeeds, then our function has succeeded, and we return the username from the file that’s now in username wrapped in an Ok. If read_to_string fails, we return the error value in the same way that we returned the error value in the match that handled the return value of File::open. However, we don’t need to explicitly say return, because this is the last expression in the function.

调用此代码的代码随后将处理获取包含用户名的 Ok 值或包含 io::ErrorErr 值。由调用代码来决定如何处理这些值。如果调用代码得到了 Err 值,例如,它可以调用 panic! 并使程序崩溃,使用一个默认用户名,或者从文件之外的其他地方查找用户名。我们没有关于调用代码实际尝试做什么的足够信息,因此我们将所有的成功或错误信息向上传播,由其进行适当处理。

The code that calls this code will then handle getting either an Ok value that contains a username or an Err value that contains an io::Error. It’s up to the calling code to decide what to do with those values. If the calling code gets an Err value, it could call panic! and crash the program, use a default username, or look up the username from somewhere other than a file, for example. We don’t have enough information on what the calling code is actually trying to do, so we propagate all the success or error information upward for it to handle appropriately.

这种传播错误的模式在 Rust 中非常常见,以至于 Rust 提供了问号运算符 ? 来使之更容易。

This pattern of propagating errors is so common in Rust that Rust provides the question mark operator ? to make this easier.

传播错误的简写:? 运算符 (The ? Operator Shortcut)

The ? Operator Shortcut

示例 9-7 显示了 read_username_from_file 的一个实现,它具有与示例 9-6 相同的功能,但此实现使用了 ? 运算符。

Listing 9-7 shows an implementation of read_username_from_file that has the same functionality as in Listing 9-6, but this implementation uses the ? operator.

#![allow(unused)]
fn main() {
{{#include ../listings/ch09-error-handling/listing-09-07/src/main.rs:here}}
}

置于 Result 值之后的 ? 被定义为与我们在示例 9-6 中定义的处理 Result 值的 match 表达式以几乎相同的方式工作。如果 Result 的值是一个 OkOk 内部的值将从该表达式返回,程序继续运行。如果值是一个 ErrErr 将从整个函数返回,就好像我们使用了 return 关键字一样,从而将错误值传播给调用代码。

The ? placed after a Result value is defined to work in almost the same way as the match expressions that we defined to handle the Result values in Listing 9-6. If the value of the Result is an Ok, the value inside the Ok will get returned from this expression, and the program will continue. If the value is an Err, the Err will be returned from the whole function as if we had used the return keyword so that the error value gets propagated to the calling code.

示例 9-6 中的 match 表达式所做的和 ? 运算符所做的之间有一个区别:被调用 ? 运算符的错误值会经过 from 函数,该函数定义在标准库的 From 特征中,用于将值从一种类型转换为另一种类型。当 ? 运算符调用 from 函数时,接收到的错误类型会被转换为当前函数返回类型中定义的错误类型。当一个函数返回一个错误类型来代表该函数可能失败的所有方式时,这非常有用,即使各部分可能因为许多不同的原因而失败。

There is a difference between what the match expression from Listing 9-6 does and what the ? operator does: Error values that have the ? operator called on them go through the from function, defined in the From trait in the standard library, which is used to convert values from one type into another. When the ? operator calls the from function, the error type received is converted into the error type defined in the return type of the current function. This is useful when a function returns one error type to represent all the ways a function might fail, even if parts might fail for many different reasons.

例如,我们可以更改示例 9-7 中的 read_username_from_file 函数,使其返回我们定义的一个名为 OurError 的自定义错误类型。如果我们还定义了 impl From<io::Error> for OurError 以从 io::Error 构造 OurError 实例,那么 read_username_from_file 体内的 ? 运算符调用将调用 from 并转换错误类型,而无需在函数中添加更多代码。

For example, we could change the read_username_from_file function in Listing 9-7 to return a custom error type named OurError that we define. If we also define impl From<io::Error> for OurError to construct an instance of OurError from an io::Error, then the ? operator calls in the body of read_username_from_file will call from and convert the error types without needing to add any more code to the function.

在示例 9-7 的上下文中,File::open 调用末尾的 ? 将把 Ok 内部的值返回给变量 username_file。如果发生错误,? 运算符将从整个函数提前返回,并将任何 Err 值交给调用代码。同样的事情也适用于 read_to_string 调用末尾的 ?

In the context of Listing 9-7, the ? at the end of the File::open call will return the value inside an Ok to the variable username_file. If an error occurs, the ? operator will return early out of the whole function and give any Err value to the calling code. The same thing applies to the ? at the end of the read_to_string call.

? 运算符消除了大量的样板代码,并使此函数的实现更简单。我们甚至可以通过在 ? 之后立即链式调用方法来进一步缩短此代码,如示例 9-8 所示。

The ? operator eliminates a lot of boilerplate and makes this function’s implementation simpler. We could even shorten this code further by chaining method calls immediately after the ?, as shown in Listing 9-8.

#![allow(unused)]
fn main() {
{{#include ../listings/ch09-error-handling/listing-09-08/src/main.rs:here}}
}

我们将 username 中新 String 的创建移动到了函数的开头;这部分没有改变。我们没有创建变量 username_file,而是将 read_to_string 的调用直接链到了 File::open("hello.txt")? 的结果上。我们在 read_to_string 调用的末尾仍然有一个 ?,当 File::openread_to_string 都成功时,我们仍然返回一个包含 usernameOk 值,而不是返回错误。其功能再次与示例 9-6 和示例 9-7 相同;这只是一种不同的、更符合人体工程学的编写方式。

We’ve moved the creation of the new String in username to the beginning of the function; that part hasn’t changed. Instead of creating a variable username_file, we’ve chained the call to read_to_string directly onto the result of File::open("hello.txt")?. We still have a ? at the end of the read_to_string call, and we still return an Ok value containing username when both File::open and read_to_string succeed rather than returning errors. The functionality is again the same as in Listing 9-6 and Listing 9-7; this is just a different, more ergonomic way to write it.

示例 9-9 展示了一种使用 fs::read_to_string 使其变得更短的方法。

Listing 9-9 shows a way to make this even shorter using fs::read_to_string.

#![allow(unused)]
fn main() {
{{#include ../listings/ch09-error-handling/listing-09-09/src/main.rs:here}}
}

将文件读入字符串是一个相当常见的操作,因此标准库提供了方便的 fs::read_to_string 函数,它可以打开文件、创建一个新 String、读取文件内容、将内容放入该 String 并返回它。当然,使用 fs::read_to_string 并没有给我们提供解释所有错误处理的机会,所以我们先用了较长的方法。

Reading a file into a string is a fairly common operation, so the standard library provides the convenient fs::read_to_string function that opens the file, creates a new String, reads the contents of the file, puts the contents into that String, and returns it. Of course, using fs::read_to_string doesn’t give us the opportunity to explain all the error handling, so we did it the longer way first.

可以在哪里使用 ? 运算符 (Where to Use the ? Operator)

Where to Use the ? Operator

? 运算符只能在返回类型与 ? 所使用的值兼容的函数中使用。这是因为 ? 运算符被定义为执行从函数中提前返回值的操作,其方式与我们在示例 9-6 中定义的 match 表达式相同。在示例 9-6 中,match 正在使用一个 Result 值,提前返回分支返回了一个 Err(e) 值。函数的返回类型必须是一个 Result,这样它才与此 return 兼容。

The ? operator can only be used in functions whose return type is compatible with the value the ? is used on. This is because the ? operator is defined to perform an early return of a value out of the function, in the same manner as the match expression we defined in Listing 9-6. In Listing 9-6, the match was using a Result value, and the early return arm returned an Err(e) value. The return type of the function has to be a Result so that it’s compatible with this return.

在示例 9-10 中,让我们看看如果在返回类型与我们使用 ? 的值类型不兼容的 main 函数中使用 ? 运算符,我们会得到什么错误。

In Listing 9-10, let’s look at the error we’ll get if we use the ? operator in a main function with a return type that is incompatible with the type of the value we use ? on.

{{#rustdoc_include ../listings/ch09-error-handling/listing-09-10/src/main.rs}}

这段代码打开一个文件,这可能会失败。? 运算符跟在 File::open 返回的 Result 值后面,但这个 main 函数的返回类型是 (),而不是 Result。当我们编译这段代码时,会得到以下错误消息:

This code opens a file, which might fail. The ? operator follows the Result value returned by File::open, but this main function has the return type of (), not Result. When we compile this code, we get the following error message:

{{#include ../listings/ch09-error-handling/listing-09-10/output.txt}}

该错误指出我们只被允许在返回 ResultOption 或另一个实现了 FromResidual 的类型的函数中使用 ? 运算符。

This error points out that we’re only allowed to use the ? operator in a function that returns Result, Option, or another type that implements FromResidual.

要修复该错误,你有两个选择。一个选择是更改函数的返回类型,使其与你使用 ? 运算符的值兼容,前提是你没有任何限制阻止这样做。另一个选择是使用 matchResult<T, E> 的方法之一,以任何合适的方式处理 Result<T, E>

To fix the error, you have two choices. One choice is to change the return type of your function to be compatible with the value you’re using the ? operator on as long as you have no restrictions preventing that. The other choice is to use a match or one of the Result<T, E> methods to handle the Result<T, E> in whatever way is appropriate.

错误消息还提到 ? 也可以用于 Option<T> 值。与在 Result 上使用 ? 一样,你只能在返回 Option 的函数中对 Option 使用 ?。在 Option<T> 上调用 ? 运算符时的行为与其在 Result<T, E> 上调用时的行为类似:如果值是 None,则 None 将在此时从函数中提前返回。如果值是 Some,则 Some 内部的值就是表达式的结果值,函数继续执行。示例 9-11 有一个寻找给定文本第一行最后一个字符的函数。

The error message also mentioned that ? can be used with Option<T> values as well. As with using ? on Result, you can only use ? on Option in a function that returns an Option. The behavior of the ? operator when called on an Option<T> is similar to its behavior when called on a Result<T, E>: If the value is None, the None will be returned early from the function at that point. If the value is Some, the value inside the Some is the resultant value of the expression, and the function continues. Listing 9-11 has an example of a function that finds the last character of the first line in the given text.

#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-11/src/main.rs:here}}
}

该函数返回 Option<char>,因为那里可能有一个字符,但也可能没有。这段代码接收 text 字符串切片参数并对其调用 lines 方法,该方法返回字符串中各行的迭代器。因为此函数想要检查第一行,所以它对迭代器调用 next 以获取迭代器的第一个值。如果 text 是空字符串,此次 next 调用将返回 None,在这种情况下我们使用 ? 停止并从 last_char_of_first_line 返回 None。如果 text 不是空字符串,next 将返回一个包含 text 第一行字符串切片的 Some 值。

This function returns Option<char> because it’s possible that there is a character there, but it’s also possible that there isn’t. This code takes the text string slice argument and calls the lines method on it, which returns an iterator over the lines in the string. Because this function wants to examine the first line, it calls next on the iterator to get the first value from the iterator. If text is the empty string, this call to next will return None, in which case we use ? to stop and return None from last_char_of_first_line. If text is not the empty string, next will return a Some value containing a string slice of the first line in text.

? 提取出了字符串切片,我们可以对该字符串切片调用 chars 来获得其字符的迭代器。我们对这第一行的最后一个字符感兴趣,所以我们调用 last 来返回迭代器中的最后一项。这是一个 Option,因为第一行可能是空字符串;例如,如果 text 以空行开头但在其他行有字符,如 "\nhi"。然而,如果第一行有最后一个字符,它将在 Some 变体中返回。中间的 ? 运算符为我们提供了一种简洁的方式来表达此逻辑,允许我们在单行内实现该函数。如果我们不能对 Option 使用 ? 运算符,我们将不得不使用更多的方法调用或 match 表达式来实现此逻辑。

The ? extracts the string slice, and we can call chars on that string slice to get an iterator of its characters. We’re interested in the last character in this first line, so we call last to return the last item in the iterator. This is an Option because it’s possible that the first line is the empty string; for example, if text starts with a blank line but has characters on other lines, as in "\nhi". However, if there is a last character on the first line, it will be returned in the Some variant. The ? operator in the middle gives us a concise way to express this logic, allowing us to implement the function in one line. If we couldn’t use the ? operator on Option, we’d have to implement this logic using more method calls or a match expression.

注意,你可以在返回 Result 的函数中对 Result 使用 ? 运算符,也可以在返回 Option 的函数中对 Option 使用 ? 运算符,但不能混用。? 运算符不会自动将 Result 转换为 Option 或反之;在这些情况下,你可以使用 Result 上的 ok 方法或 Option 上的 ok_or 方法显式执行转换。

Note that you can use the ? operator on a Result in a function that returns Result, and you can use the ? operator on an Option in a function that returns Option, but you can’t mix and match. The ? operator won’t automatically convert a Result to an Option or vice versa; in those cases, you can use methods like the ok method on Result or the ok_or method on Option to do the conversion explicitly.

到目前为止,我们使用的所有 main 函数都返回 ()main 函数很特殊,因为它是可执行程序的入口点和出口点,为了使程序的行为符合预期,对其返回类型有一定的限制。

So far, all the main functions we’ve used return (). The main function is special because it’s the entry point and exit point of an executable program, and there are restrictions on what its return type can be for the program to behave as expected.

幸运的是,main 也可以返回 Result<(), E>。示例 9-12 包含了来自示例 9-10 的代码,但我们已将 main 的返回类型更改为 Result<(), Box<dyn Error>> 并在末尾添加了返回值 Ok(())。这段代码现在可以编译了。

Luckily, main can also return a Result<(), E>. Listing 9-12 has the code from Listing 9-10, but we’ve changed the return type of main to be Result<(), Box<dyn Error>> and added a return value Ok(()) to the end. This code will now compile.

{{#rustdoc_include ../listings/ch09-error-handling/listing-09-12/src/main.rs}}

Box<dyn Error> 类型是一个特征对象,我们将在第 18 章的“使用特征对象实现不同类型间的抽象行为”中讨论。目前,你可以将 Box<dyn Error> 理解为“任何类型的错误”。在返回类型为 Box<dyn Error>main 函数中对 Result 值使用 ? 是被允许的,因为它允许任何 Err 值被提前返回。尽管此 main 函数的主体只会返回 std::io::Error 类型的错误,但通过指定 Box<dyn Error>,即使向 main 主体添加了更多返回其他错误的代码,此签名也将继续保持正确。

The Box<dyn Error> type is a trait object, which we’ll talk about in “Using Trait Objects to Abstract over Shared Behavior” in Chapter 18. For now, you can read Box<dyn Error> to mean “any kind of error.” Using ? on a Result value in a main function with the error type Box<dyn Error> is allowed because it allows any Err value to be returned early. Even though the body of this main function will only ever return errors of type std::io::Error, by specifying Box<dyn Error>, this signature will continue to be correct even if more code that returns other errors is added to the body of main.

main 函数返回 Result<(), E> 时,如果 main 返回 Ok(()),可执行文件将以 0 值退出;如果 main 返回 Err 值,可执行文件将以非零值退出。用 C 编写的可执行文件在退出时返回整数:成功退出的程序返回整数 0,报错的程序返回某些非 0 的整数。Rust 也从可执行文件返回整数以兼容此惯例。

When a main function returns a Result<(), E>, the executable will exit with a value of 0 if main returns Ok(()) and will exit with a nonzero value if main returns an Err value. Executables written in C return integers when they exit: Programs that exit successfully return the integer 0, and programs that error return some integer other than 0. Rust also returns integers from executables to be compatible with this convention.

main 函数可以返回任何实现了 标准库 std::process::Termination 特征 的类型,该特征包含一个返回 ExitCodereport 函数。关于为自定义类型实现 Termination 特征的更多信息,请查阅标准库文档。

The main function may return any types that implement the std::process::Termination trait, which contains a function report that returns an ExitCode. Consult the standard library documentation for more information on implementing the Termination trait for your own types.

既然我们已经讨论了调用 panic! 或返回 Result 的细节,让我们回到如何决定在哪些情况下使用哪种方式是合适的话题。

Now that we’ve discussed the details of calling panic! or returning Result, let’s return to the topic of how to decide which is appropriate to use in which cases.