@ -4,7 +4,7 @@
> < br >
> commit e6d6caab41471f7115a621029bd428a812c5260e
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反映 的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而操作就 失败,这时我们可能想要创建这个文件而不是终止进程。
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应 的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而操作失败,这时我们可能想要创建这个文件而不是终止进程。
回忆一下第二章 “使用 `Result` 类型来处理潜在的错误” 部分中的那个 `Result` 枚举,它定义有如下两个成员,`Ok` 和 `Err` :
@ -21,7 +21,7 @@ enum Result<T, E> {
让我们调用一个返回 `Result` 的函数,因为它可能会失败:如列表 9-2 所示打开一个文件:
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust
use std::fs::File;
@ -31,7 +31,7 @@ fn main() {
}
```
< span class = "caption" > Listing 9-2: Opening a file < / span >
< span class = "caption" > 列表 9-2: 打开文件 < / span >
如何知道 `File::open` 返回一个 `Result` 呢?我们可以查看标准库 API 文档,或者可以直接问编译器!如果给 `f` 某个我们知道 ** 不是** 函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们 `f` 的类型 ** 应该** 是什么,为此我们将 `let f` 语句改为:
@ -41,7 +41,7 @@ let f: u32 = File::open("hello.txt");
现在尝试编译会给出如下错误:
```
```text
error[E0308]: mismatched types
--> src/main.rs:4:18
|
@ -53,15 +53,15 @@ error[E0308]: mismatched types
= note: found type `std::result::Result<std::fs::File, std::io::Error>`
```
这就告诉我们了`File::open`函数的返回值类型是`Result < T , E >` 。这里泛型参数`T`放入了成功值的类型`std::fs::File`,它是一个文件句柄。`E`被用在失败值上其类型是`std::io::Error`。
这就告诉我们了 `File::open` 函数的返回值类型是 `Result<T, E>` 。这里泛型参数 `T` 放入了成功值的类型 `std::fs::File` ,它是一个文件句柄。`E` 被用在失败值上时 其类型是 `std::io::Error` 。
这个返回值类型说明 `File::open` 调用可能会成功并返回一个可以进行读写的文件句柄。这个函数也可能会失败:例如,文件可能并不存在,或者可能没有访问文件的权限。`File::open` 需要一个方式告诉我们是成功还是失败,并同时提供给我们文件句柄或错误信息。而这些信息正是 `Result` 枚举可以提供的。
当 `File::open` 成功的情况下,变量 `f` 的值将会是一个包含文件句柄的 `Ok` 实例。在失败的情况下,`f` 会是一个包含更多关于出现了何种错误信息的 `Err` 实例。
我们需要在列表 9-2 的代码中增加根据`File::open`返回值进行不同处理的逻辑。列表 9-3 展示了一个处理`Result`的基本工具:第六章学习过的`match` 表达式。
我们需要在列表 9-2 的代码中增加根据 `File::open` 返回值进行不同处理的逻辑。列表 9-3 展示了一个使用基本工具处理 `Result` 的例子:第六章学习过的 `match` 表达式。
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust,should_panic
use std::fs::File;
@ -78,16 +78,15 @@ fn main() {
}
```
< span class = "caption" > Listing 9-3: Using a `match` expression to handle the
`Result` variants we might have</ span >
< span class = "caption" > 列表 9-3: 使用 `match` 表达式处理可能的 `Result` 成员</ span >
注意与 `Option` 枚举一样,`Result` 枚举和其成员也被导入到了 prelude 中,所以就不需要在 `match` 分支中的 `Ok` 和 `Err` 之前指定 `Result::` 。
这里我们告诉 Rust 当结果是`Ok`,返回`Ok`成员中的`file`值,然后将这个文件句柄赋值给变量`f`。`match`之后,我们可以利用这个文件句柄来进行读写。
这里我们告诉 Rust 当结果是 `Ok` 时 ,返回 `Ok` 成员中的 `file` 值,然后将这个文件句柄赋值给变量 `f` 。`match` 之后,我们可以利用这个文件句柄来进行读写。
`match` 的另一个分支处理从 `File::open` 得到 `Err` 值的情况。在这种情况下,我们选择调用 `panic!` 宏。如果当前目录没有一个叫做 *hello.txt* 的文件,当运行这段代码时会看到如下来自 `panic!` 宏的输出:
```
```text
thread 'main' panicked at 'There was a problem opening the file: Error { repr:
Os { code: 2, message: "No such file or directory" } }', src/main.rs:8
```
@ -96,7 +95,7 @@ Os { code: 2, message: "No such file or directory" } }', src/main.rs:8
列表 9-3 中的代码不管 `File::open` 是因为什么原因失败都会 `panic!` 。我们真正希望的是对不同的错误原因采取不同的行为:如果 `File::open ` 因为文件不存在而失败,我们希望创建这个文件并返回新文件的句柄。如果 `File::open` 因为任何其他原因失败,例如没有打开文件的权限,我们仍然希望像列表 9-3 那样 `panic!` 。让我们看看列表 9-4, 其中 `match` 增加了另一个分支:
< span class = "filename" > Filename : src/main.rs< / span >
< span class = "filename" > 文件名 : src/main.rs< / span >
```rust,ignore
use std::fs::File;
@ -128,18 +127,17 @@ fn main() {
}
```
< span class = "caption" > Listing 9-4: Handling different kinds of errors in
different ways< / span >
< span class = "caption" > 列表 9-4: 使用不同的方式处理不同类型的错误< / span >
`File::open` 返回的 `Err` 成员中的值类型 `io::Error` ,它是一个标准库中提供的结构体。这个结构体有一个返回 `io::ErrorKind` 值的 `kind` 方法可供调用。`io::ErrorKind` 是一个标准库提供的枚举,它的成员对应 `io` 操作可能导致的不同错误类型。我们感兴趣的成员是 `ErrorKind::NotFound` ,它代表尝试打开的文件并不存在。
条件`if error.kind() == ErrorKind::NotFound`被称作 *match guard* :它是一个进一步完善`match`分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹配会继续并考虑`match`中的下一个分支。模式中的`ref`是必须的,这样`error`就不会被移动到 guard 条件中而只是仅仅 引用它。第十八章会详细解释为什么在模式中使用`ref`而不是` & `来获取一个引用。简而言之,在模式的上下文中,`& `匹配一个引用并返回它的值,而`ref`匹配一个值并返回一个引用。
条件 `if error.kind() == ErrorKind::NotFound` 被称作 *match guard* :它是一个进一步完善 `match` 分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹配会继续并考虑 `match` 中的下一个分支。模式中的 `ref` 是必须的,这样 `error` 就不会被移动到 guard 条件中而仅仅 只是引用它。第十八章会详细解释为什么在模式中使用 `ref` 而不是 `&` 来获取一个引用。简而言之,在模式的上下文中,`& ` 匹配一个引用并返回它的值,而 `ref` 匹配一个值并返回一个引用。
在 match guard 中我们想要检查的条件是`error.kind()`是否是`ErrorKind`枚举的`NotFound`成员。如果是,尝试用`File::create`创建文件。然而`File::create`也可能会失败,我们 还需要增加一个内部`match`语句。当文件不能被打开,会打印出一个不同的错误信息。外部`match`的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。
在 match guard 中我们想要检查的条件是 `error.kind()` 是否是 `ErrorKind` 枚举的 `NotFound` 成员。如果是,尝试用 `File::create` 创建文件。然而 `File::create` 也可能会失败,还需要增加一个内部 `match` 语句。当文件不能被打开,会打印出一个不同的错误信息。外部 `match` 的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。
### 失败时 panic 的捷径:`unwrap` 和 `expect`
`match` 能够胜任它的工作,不过它可能有点冗长并且并 不总是能很好的表明意图。`Result< T , E > `类型定义了很多辅助方法来处理各种情况。其中之一叫做`unwrap`,它的实现就类似于列表 9-3 中的`match`语句。如果`Result`值是成员`Ok`, `unwrap`会返回`Ok`中的值。如果`Result`是成员`Err`, `unwrap`会为我们调用`panic!`。
`match` 能够胜任它的工作,不过它可能有点冗长并且不总是能很好的表明意图。`Result< T , E > ` 类型定义了很多辅助方法来处理各种情况。其中之一叫做 `unwrap` ,它的实现就类似于列表 9-3 中的 `match` 语句。如果 `Result` 值是成员 `Ok`, `unwrap` 会返回 `Ok` 中的值。如果 `Result` 是成员 `Err`, `unwrap` 会为我们调用 `panic!` 。
```rust,should_panic
use std::fs::File;
@ -151,7 +149,7 @@ fn main() {
如果调用这段代码时不存在 *hello.txt* 文件,我们将会看到一个 `unwrap` 调用 `panic!` 时提供的错误信息:
```
```text
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error {
repr: Os { code: 2, message: "No such file or directory" } }',
/stable-dist-rustc/build/src/libcore/result.rs:868
@ -167,9 +165,9 @@ fn main() {
}
```
`expect` 与`unwrap`的使用方式一样:返回文件句柄或调用`panic!`宏。`expect`用来调用`panic!`的错误信息将会作为传递给`expect`的参数,而不像`unwrap`那样使用默认的`panic!` 信息。它看起来像这样:
`expect` 与 `unwrap` 的使用方式一样:返回文件句柄或调用 `panic!` 宏。`expect` 用来调用 `panic!` 的错误信息将会作为参数传递给 `expect` ,而不像`unwrap` 那样使用默认的 `panic!` 信息。它看起来像这样:
```
```text
thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
2, message: "No such file or directory" } }',
/stable-dist-rustc/build/src/libcore/result.rs:868
@ -203,16 +201,15 @@ fn read_username_from_file() -> Result<String, io::Error> {
}
```
< span class = "caption" > Listing 9-5: A function that returns errors to the
calling code using `match` </ span >
< span class = "caption" > 列表 9-5: 一个函数使用 `match` 将错误返回给代码调用者</ span >
首先让我们看看函数的返回值:`Result< String , io::Error > `。这意味着函数返回一个`Result< T , E > `类型的值,其中泛型参数`T`的具体类型是`String`,而`E`的具体类型是`io::Error` 。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含`String`的`Ok`值————函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个`Err`值,它储存了一个包含更多这个问题相关信息的`io::Error`实例。我们选择`io::Error` 作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:`File::open`函数和`read_to_string`方法。
首先让我们看看函数的返回值:`Result< String , io::Error >`。这意味着函数返回一个 `Result<T, E>` 类型的值,其中泛型参数 `T` 的具体类型是 `String` ,而 `E` 的具体类型是 `io::Error` 。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含 `String` 的 `Ok` 值————函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个 `Err` 值,它储存了一个包含更多这个问题相关信息的 `io::Error` 实例。这里选择 `io::Error` 作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:`File::open` 函数和 `read_to_string` 方法。
函数体以 `File::open` 函数开头。接着使用 `match` 处理返回值 `Result` ,类似于列表 9-3 中的 `match` ,唯一的区别是不再当 `Err` 时调用 `panic!` ,而是提早返回并将 `File::open` 返回的错误值作为函数的错误返回值传递给调用者。如果 `File::open` 成功了,我们将文件句柄储存在变量 `f` 中并继续。
接着我们在变量 `s` 中创建了一个新 `String` 并调用文件句柄 `f` 的 `read_to_string` 方法来将文件的内容读取到 `s` 中。`read_to_string` 方法也返回一个 `Result` 因为它也可能会失败:哪怕是 `File::open` 已经成功了。所以我们需要另一个 `match` 来处理这个 `Result` :如果 `read_to_string` 成功了,那么这个函数就成功了,并返回文件中的用户名,它现在位于被封装进 `Ok` 的 `s` 中。如果`read_to_string` 失败了,则像之前处理 `File::open` 的返回值的 `match` 那样返回错误值。并不需要显式的调用 `return` ,因为这是函数的最后一个表达式。
调用这个函数的代码最终会得到一个包含用户名的`Ok`值,亦 或一个包含`io::Error`的`Err`值。我们无从得知调用者会如何处理这些值。例如,如果他们得到了一个`Err`值,他们可能会选择`panic!`并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适处理方法。
调用这个函数的代码最终会得到一个包含用户名的 `Ok` 值,或者 一个包含 `io::Error` 的 `Err` 值。我们无从得知调用者会如何处理这些值。例如,如果他们得到了一个 `Err` 值,他们可能会选择 `panic!` 并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适处理方法。
这种传播错误的模式在 Rust 是如此的常见,以至于有一个更简便的专用语法:`?`。
@ -233,8 +230,7 @@ fn read_username_from_file() -> Result<String, io::Error> {
}
```
< span class = "caption" > Listing 9-6: A function that returns errors to the
calling code using `?` </ span >
< span class = "caption" > 列表 9-6: 一个使用 `?` 向调用者返回错误的函数</ span >
`Result` 值之后的 `?` 被定义为与列表 9-5 中定义的处理 `Result` 值的 `match` 表达式有着完全相同的工作方式。如果 `Result` 的值是 `Ok` ,这个表达式将会返回 `Ok` 中的值而程序将继续执行。如果值是 `Err` , `Err` 中的值将作为整个函数的返回值,就好像使用了 `return` 关键字一样,这样错误值就被传播给了调用者。
@ -276,7 +272,6 @@ fn main() {
that doesn't return a result is STILL confusing. Since we want to only explain
`?` now, I've changed the example, but if you try running this code you WON'T
get the error message below.
I'm bugging people to try and get
https://github.com/rust-lang/rust/issues/35946 fixed soon, hopefully before this
chapter gets through copy editing-- at that point I'll make sure to update this
@ -284,7 +279,7 @@ error message. /Carol -->
当编译这些代码,会得到如下错误信息:
```
```text
error[E0308]: mismatched types
-->
|