mirror of
https://github.com/gnu4cn/rust-lang-zh_CN.git
synced 2026-08-19 04:33:27 +08:00
Updated src/error_handling/result.md'.
This commit is contained in:
6
projects/error_propagation_demo/Cargo.toml
Normal file
6
projects/error_propagation_demo/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "error_propagation_demo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
1
projects/error_propagation_demo/hello.txt
Normal file
1
projects/error_propagation_demo/hello.txt
Normal file
@@ -0,0 +1 @@
|
||||
hector
|
||||
16
projects/error_propagation_demo/src/main.rs
Normal file
16
projects/error_propagation_demo/src/main.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use std::{fs, io};
|
||||
|
||||
fn read_username_from_file() -> Result<String, io::Error> {
|
||||
fs::read_to_string("hello.txt")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = match read_username_from_file() {
|
||||
Ok(res) => res,
|
||||
Err(e) => format! ("{e:?}")
|
||||
};
|
||||
|
||||
println! ("{result}");
|
||||
}
|
||||
|
||||
|
||||
6
projects/unwrap_demo/Cargo.toml
Normal file
6
projects/unwrap_demo/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "unwrap_demo"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
6
projects/unwrap_demo/src/main.rs
Normal file
6
projects/unwrap_demo/src/main.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
use std::fs::File;
|
||||
|
||||
fn main() {
|
||||
let greeting_file = File::open("hello.txt")
|
||||
.expect("hello.txt 应包含在此项目中");
|
||||
}
|
||||
@@ -113,38 +113,33 @@ fn main() {
|
||||
|
||||
> **对 `Result<T, E>` 使用 `match` 的替代方案**
|
||||
>
|
||||
> 这可真是不少的 `match`!`match` 表达式非常有用,但也非常原始。在第 13 章中,咱们将了解与许多定义在 `Result<T, E>` 上方法一起使用的闭包。在与咱们的代码中处理 `Result<T, E>` 值时,这些方法比使用 `match` 更为简洁。
|
||||
> 这可真是不少的 `match`!`match` 表达式非常有用但也非常原始。在第 13 章中,咱们将学习闭包,其会与定义在 `Result<T, E>` 上的许多方法一起使用。在处理咱们代码中的 `Result<T, E>` 值时,这些方法比使用 `match` 更为简洁。
|
||||
>
|
||||
> 例如,下面是编写与清单 9-5 中所示同一逻辑的另一种方法,这次使用了闭包及 `unwrap_or_else` 这个方法:
|
||||
|
||||
```rust
|
||||
use std::fs::File;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
fn main() {
|
||||
let greeting_file = File::open("hello.txt").unwrap_or_else(|e| {
|
||||
if e.kind() == ErrorKind::NotFound {
|
||||
File::create("hello.txt").unwrap_or_else(|error| {
|
||||
panic! ("创建文件时发生问题:{:?}", error);
|
||||
})
|
||||
} else {
|
||||
panic! ("打开文件时出现问题:{:?}", e);
|
||||
}
|
||||
});
|
||||
|
||||
println! ("{:?}", greeting_file);
|
||||
}
|
||||
```
|
||||
|
||||
> 虽然这段代码的行为与清单 9-5 相同,但他未包含任何的 `match` 表达式,而读起来更简洁。在咱们读完第 13 章后,请再来看这个示例,并在标准库文档中找到 `unwrap_or_else` 这个方法。在咱们处理错误时,还有更多的这些方法,可以清理庞大的 `match` 匹配表达式。
|
||||
> 例如,下面是编写与清单 9-5 中所示同一逻辑的另一种方式,这次使用闭包及 `unwrap_or_else` 方法:
|
||||
>
|
||||
> ```rust
|
||||
> use std::fs::File;
|
||||
> use std::io::ErrorKind;
|
||||
>
|
||||
> fn main() {
|
||||
> let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
|
||||
> if e.kind() == ErrorKind::NotFound {
|
||||
> File::create("hello.txt").unwrap_or_else(|error| {
|
||||
> panic! ("创建文件时发生问题:{error:?}");
|
||||
> })
|
||||
> } else {
|
||||
> panic! ("打开文件时出现问题:{error:?}");
|
||||
> }
|
||||
> });
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
虽然这段代码有着与清单 9-5 相同的行为,但他未包含任何 `match` 表达式,进而读起来更清晰。请在咱们读完第 13 章后回到这个示例,并在标准库文档中查找 `unwrap_or_else` 方法。在咱们处理错误时,还有更多的这些方法可以清理庞大、嵌套的 `match` 表达式。
|
||||
|
||||
|
||||
### 出错时死机的快捷方式:`unwrap` 与 `expect`
|
||||
### 出错时终止运行的快捷方式
|
||||
|
||||
**Shortcuts for Panic on Error: `unwrap` and `expect`**
|
||||
|
||||
|
||||
使用 `match` 可以很好地工作,但这样做可能有点啰嗦,而且并不总是很好地传达意图。`Result<T, E>` 这个类型,定义了许多用于完成各种更具体任务的辅助方法。`unwrap` 方法是个实现了与我们在清单 9-4 中,所编写的 `match` 表达式一样的快捷方法。在结果值为 `Ok` 变种时,`unwrap` 将返回 `Ok` 内的值。在结果值为 `Err` 变种时,`unwrap` 将为我们调用 `panic!` 宏。下面是个 `unwrap` 的示例:
|
||||
使用 `match` 效果很好,但他可能可能有点冗长,并且并不总是很好地传达意图。`Result<T, E>` 类型有许多定义在其上的辅助方法,以执行各种更具体的任务。`unwrap` 方法是个快捷方法,被实现为就像我们在清单 9-4 中编写的 `match` 表达式。当 `Result` 值为 `Ok` 变种时,`unwrap` 将返回 `Ok` 内的值。当 `Result` 为 `Err` 变种时,`unwrap` 将为我们调用 `panic!` 宏。下面是个 `unwrap` 的实际示例:
|
||||
|
||||
|
||||
文件名:`src/main.rs`
|
||||
@@ -157,15 +152,15 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
若我们在没有 `hello.txt` 文件的情况下运行这段代码,我们将看到 `unwrap` 方法调用 `panic!` 的错误消息:
|
||||
若我们在没有 `hello.txt` 文件下运行这段代码,我们将看到一条来自 `unwrap` 方法所发起的 `panic!` 调用的错误消息:
|
||||
|
||||
|
||||
```console
|
||||
thread 'main' panicked at src/main.rs:5:49:
|
||||
thread 'main' (536746) 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` 的语法如下:
|
||||
同样,`expect` 方法还允许我们选择 `panic!` 错误消息。使用 `expect` 而不是 `unwrap` 并提供良好的错误消息,可以传达我们的意图并使追溯终止运行根源变得更为容易。`expect` 的语法如下:
|
||||
|
||||
|
||||
文件名:`src/main.rs`
|
||||
@@ -179,28 +174,25 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
我们以与 `unwrap` 相同方式使用 `expect`:返回该文件的句柄或调用 `panic!` 宏。`expect` 在其对 `panic!` 的调用中,所使用的错误消息将是我们传递给 `expect` 的参数,而不是 `unwrap` 使用的默认 `panic!` 消息。其看起来像下面这样:
|
||||
我们以与 `unwrap` 相同方式使用 `expect`:返回文件句柄或调用 `panic!` 宏。`expect` 在对 `panic!` 的调用中使用的错误消息,将是我们传递给 `expect` 的参数,而不是 `unwrap` 使用的默认 `panic!` 消息。其看起来像下面这样:
|
||||
|
||||
|
||||
```console
|
||||
thread 'main' panicked at src/main.rs:5:10:
|
||||
thread 'main' (539093) panicked at src/main.rs:5:10:
|
||||
hello.txt 应包含在此项目中: Os { code: 2, kind: NotFound, message: "No such file or directory" }
|
||||
```
|
||||
|
||||
在生产质量的代码中,大多数 Rust 公民都会选择 `expect` 而不是 `unwrap`,并提供更多有关为什么该操作会总是成功的上下文消息。这样,在咱们的假设即使被证明是错误时,咱们也有更多用于调试的信息。
|
||||
|
||||
在生产质量代码中,大多数 Rustaceans 都会选择 `expect` 而不是 `unwrap`,并会提供更多有关为何操作被认为总是会成功的背景信息。这样,当咱们的假设即使被证明是错的时,咱们也会有更多在调试过程中使用的信息。
|
||||
|
||||
|
||||
## 传播错误
|
||||
|
||||
**Propagating Errors**
|
||||
|
||||
|
||||
在某个函数的实现,调用了可能失败的某些代码时,与其在该函数本身中处理错误,咱们可将错误返回给调用代码,让他决定要做些什么。这就是所谓的 *传播,propagating* 错误,而将更多控制权交给了调用代码,相比咱们代码的上下文,调用代码中可能有更多决定如何处理错误的信息或逻辑。
|
||||
|
||||
例如,下面清单 9-6 显示了一个从文件中读取用户名的函数。在该文件不存在或无法读取时,这个函数将把这些错误,返回给调用该函数的代码。
|
||||
在函数的实现调用了可能失败的某些代码时,与其在该函数本身内处理错误,咱们可返回错误给调用代码,从而其可以决定要做些什么。这称为 *传播,propagating* 错误并赋予更多控制权给调用代码,相比咱们在咱们的代码上下文中有的可用信息或逻辑,调用代码中可能有更多决定错误应如何处理的信息或逻辑。
|
||||
|
||||
例如,下面清单 9-6 显示了一个读取文件中用户名的函数。当文件不存在或无法读取时,该函数将返回这些错误给调用该函数的代码。
|
||||
|
||||
|
||||
<a name="listing_9-6"></a>
|
||||
```rust
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
@@ -208,7 +200,7 @@ use std::io::{self, Read};
|
||||
fn read_username_from_file() -> Result<String, io::Error> {
|
||||
let username_file_result = File::open("hello.txt");
|
||||
|
||||
let mut username_file = match username_file_result {
|
||||
let mut username_file = match username_file_result {
|
||||
Ok(file) => file,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
@@ -222,31 +214,26 @@ fn read_username_from_file() -> Result<String, io::Error> {
|
||||
}
|
||||
```
|
||||
|
||||
*清单 9-6:使用 `match` 表达式将错误返回给调用代码的一个函数*
|
||||
**清单 9-6**:使用 `match` 表达式返回错误给调用代码的函数
|
||||
|
||||
这个函数可以更简短的方式编写,但为了探讨错误处理,我们将以手动方式完成其绝大部分开始;最后,我们将展示更简短的方式。我们首先来看看函数的返回类型:`Result<String, io::Error>`。这意味着该函数正返回一个 `Result<T, E>` 类型的值,其中泛型参数 `T` 已以具体类型 `String` 填入,且泛型 `E` 已以具体类型 `io::Error` 填入。
|
||||
|
||||
当这个函数在没有任何问题下成功时,调用该函数的代码将收到一个 `Ok` 值,其中保存着一个 `String` -- 该函数从文件中读取到的 `username`。当该函数遇到任何问题时,调用代码将收到一个 `Err` 值,该值保存着 `io::Error` 的一个实例,其包含有关问题为何的更多信息。我们选择 `io::Error` 作为这个函数的返回类型,因为他恰好同时是我们在这个函数的主体中,调用的两个可能失败的操作:`File::open` 函数与 `read_to_string` 方法返回的错误值类型。
|
||||
|
||||
该函数的主体以调用 `File::open` 函数开始。然后,我们以一个类似于清单 9-4 中的 `match` 处理 `Result` 值。当 `File::open` 成功时,模式变量 `file` 中的文件句柄成为可变变量 `username_file` 中的值而该函数会继续。在 `Err` 情形下,我们没有调用 `panic!`,而是使用 `return` 关键字完全从该函数提前返回,并传递 `File::open` 中的错误值,其现在位于模式变量 `e` 中,回给调用代码。
|
||||
|
||||
因此,当我们在 `username_file` 中有文件句柄时,该函数随后会在变量 `username` 中创建一个新的 `String`,并调用 `username_file` 中的文件句柄上的 `read_to_string` 方法,读取该文件的内容到 `username` 中。`read_to_string` 方法也会返回 `Result`,因为即使 `File::open` 成功了,他也可能失败。因此,我们需要另一个 `match` 表达式来处理这个 `Result`:当 `read_to_string` 成功时,那么我们的函数就成功了,进而我们返回文件中的用户名,其现在位于封装在 `Ok` 中的 `username` 中。当 `read_to_string` 失败时,我们以返回处理 `File::open` 返回值的 `match` 表达式中的错误值的同一方式返回这个错误值。不过,我们不需要显式指明 `return`,因为这是函数中的最后一个表达式。
|
||||
|
||||
调用这段代码的代码随后将处理获取到要么包含用户名的 `Ok` 值,要么包含 `io::Error` 的 `Err` 值(译注:两种情况)。对这两种值要做些什么由调用代码自行决定。当调用代码得到 `Err` 值时,他可以调用 `panic!` 并崩溃程序、使用默认用户名,或从该文件以外的其他地方查找用户名。由于我们没有调用代码到底要做什么的足够信息,因此我们向上传播所有成功或错误的信息,以供其恰当处理。
|
||||
|
||||
这种传播错误的模式在 Rust 中是如此的常见,以至 Rust 提供了问号操作符 `?`,the question mark operator,使其变得更容易。
|
||||
|
||||
|
||||
这个函数可以更简短方式编写,但为探索错误处理,我们将从手动方式完成他开始;在最后,我们将展示那种更简短的方式。我们先看看这个函数的返回类型:`Result<String, io::Error>`。这意味着该函数返回的是个 `Result<T, E>` 类型的值,其中泛型参数 `T` 已填充为具体类型 `String`,且泛型 `E` 已填充为具体类型 `io::Error`。
|
||||
### `?` 操作符快捷方式
|
||||
|
||||
在这个函数在没有任何问题下成功执行时,调用该函数的代码将收到保存着一个字符串的 `Ok` 值 -- 及该函数从文件中读取到的 `username`。在该函数遇到任何问题时,调用代码将收到保存着一个 `io::Error` 实例的 `Err` 值,该实例包含了问题为何的更多信息。我们选择 `io::Error` 作为此函数的返回类型,是因为在此函数的主体中,我们调用的两个操作:`File::open` 函数与 `read_too_string` 方法,都可能失败,而这两个操作返回的错误值类型,恰好都是 `io::Error`。
|
||||
|
||||
|
||||
该函数的主体以调用 `File::open` 函数开始。然后,我们以一个与清单 9-4 中类似的 `match` 处理 `Result` 值。在 `File::open` 成功时,模式变量 `file` 中的文件句柄,就成为可变变量 `username_file` 中的值,同时函数继续执行。在 `Err` 情形下,我们没有调用 `panic!`,而是使用 `return` 关键字提前从该函数整个返回,并将 `File::open` 中的错误值,即现在模式变量 `e` 中的值,作为该函数的错误值传回调用代码。
|
||||
|
||||
|
||||
因此,若我们在 `username_file` 中有了个文件句柄,该函数就会在变量 `username` 中创建出一个新的 `String`,然后调用 `username_file` 中文件句柄上的 `read_to_string` 方法,将该文件的内容读入 `username`。`read_to_string` 方法也会返回一个 `Result`,因为即使 `File::open` 成功了,他也可能失败。因此,我们需要另一个 `match` 表达式处理这个 `Result`:在 `read_to_string` 成功时,那么我们的函数就成功了,同时我们会返回该文件中的用户名,现在是封装在一个 `Ok` 里的 `username` 中。在 `read_to_string` 失败时,我们会以我们处理 `File::open` 返回值的 `match` 表达式中同一方式,返回这个错误值。不过,我们无需说明 `return`,因为这是该函数中的最后一个表达式。
|
||||
|
||||
|
||||
调用这段代码的代码,将处理获取到包含用户名的一个 `Ok` 值,或处理包含 `io::Error` 的一个 `Err` 值。调用代码将自行决定对这些值执行什么操作。在调用代码得到一个 `Err` 值时,他可以调用 `panic!` 使程序崩溃、使用一个默认用户名,或者从文件以外的其他地方查找用户名。我们没有有关调用代码到底要做什么的足够,因此我们会将所有的成功或错误信息向上传播,以便其进行适当处理。
|
||||
|
||||
这种传播错误的模式在 Rust 中非常常见,因此 Rust 提供了问号操作符 `?`,the question mark operator,使其更容易。
|
||||
|
||||
|
||||
### 传播错误的捷径:`?` 操作符
|
||||
|
||||
**A Shortcut for Propagating Errors: the `?` Operator**
|
||||
|
||||
下面清单 9-7 显示了 `read_username_from_file` 的一种实现,其功能与清单 9-6 中的相同,但这一实现使用了 `?` 操作符。
|
||||
下面清单 9-7 显示了 `read_username_from_file` 的一种实现,其有着与清单 9-6 中同样的功能,但这一实现使用 `?` 操作符。
|
||||
|
||||
<a name="listing_9-7"></a>
|
||||
文件名:`src/main.rs`
|
||||
|
||||
```rust
|
||||
@@ -261,21 +248,20 @@ fn read_username_from_file() -> Result<String, io::Error> {
|
||||
}
|
||||
```
|
||||
|
||||
*清单 9-7:使用 `?` 操作符将错误返回给调用代码的一个函数*
|
||||
**清单 9-7**:使用 `?` 操作符返回错误给调用代码的函数
|
||||
|
||||
放在某个 `Result` 值后面的 `?`,被定义与清单 9-6 中,我们为处理 `Result` 值而定义的 `match` 表达式工作方式几乎相同。在 `Result` 的值是个 `Ok`,则这个 `Ok` 中的值将从该表达式返回,同时程序将继续运行。在 `Result` 的值是个 `Err` 时,那么这个 `Err` 将从整个函数中返回,就像我们曾使用的那个 `return` 关键字一样,如此错误值就得以传播到调用代码。
|
||||
|
||||
|
||||
清单 9-6 中 `match` 表达式的作用,与 `?` 运算符的作用有所不同:调用 `?` 运算符的错误值,会经过标准库中 `From` 特质中定义的 `from` 函数,该函数被用来将值从一种类型,转换为另一类型。当 `?` 运算符调用 `from` 函数时,收到的错误类型会被转换为当前函数返回类型中,所定义的错误类型。在某个函数返回表示函数可能失败所有方式的一种类型,即使各个部分可能因多种不同原因失败时,这种方法非常有用。
|
||||
|
||||
例如,我们可以将清单 9-7 中的 `read_username_from_file` 函数,修改为返回我们定义的名为 `OurError` 的自定义错误类型。在我们也为 `OurError` 定义了 `impl From<io::Error>`,以从 `io::Error` 构造出一个 `OurError` 实例时,那么 `read_username_from_file` 主体中的 `?` 操作符调用,就将调用 `from`,并无需在函数中添加更多代码下,转换这些错误类型。
|
||||
|
||||
|
||||
在清单 9-7 的上下文中,`File::open` 调用结尾处的 `?` 操作符,将把一个 `Ok` 内的值,返回给变量 `username_file`。在发生错误时,`?` 操作符将从整个函数提前返回,并将 `Err` 值返回给调用代码。同样的情况也适用于 `read_to_string` 调用结束处的那个 `?` 操作符。
|
||||
|
||||
`?` 操作符消除了大量模板代码,而使这个函数的实现更简单。如下清单 9-8 所示,通过在 `?` 操作符后立即链接这些方法调用,我们甚至能进一步缩短这段代码。
|
||||
放在 `Result` 值后面的 `?`,被定义为以几乎与 [清单 9-6](#listing_9-6) 中我们定义的,处理 `Result` 值的 `match` 表达式的同一方式工作。当 `Result` 的值为 `Ok` 时,`Ok` 中的值将从这一表达式返回,并且程序将继续。当 `Result` 的值为 `Err` 时,`Err` 将从整个函数中返回,就像我们使用了 `return` 关键字一样,以便错误值得以传播到调用代码。
|
||||
|
||||
清单 9-6 中的 `match` 表达式执行的操作与 `?` 操作符之间有个差异:于其上调用 `?` 操作符的错误值,会经历在标准库中的 `From` 特质中定义的 `from` 函数,该函数被用于转换一种类型的值为另一类型。当 `?` 操作符调用 `from` 函数时,接收到的错误类型会被转换为定义在当前函数返回类型中的错误类型。在即使各个部分可能因许多不同原因而失败,函数仍返回表示函数可能失败的所有方式的一种类型时,这一点非常有用。
|
||||
|
||||
例如,我们可以修改清单 9-7 中的 `read_username_from_file` 函数,为返回我们定义的一个名为 `OurError` 的自定义错误类型。当我们也为 `OurError` 定义了 `impl From<io::Error>`,以从一个 `io::Error` 构造一个 `OurError` 实例时,那么 `read_username_from_file` 主体中的 `?` 操作符调用将调用 `from`,并会在无需添加任何代码到该函数下转换错误类型。
|
||||
|
||||
在清单 9-7 的上下文中,`File::open` 调用末尾处的 `?` 操作符将返回 `Ok` 内的值给变量 `username_file`。当错误发生时,`?` 操作符将提前从整个函数返回并给予任何 `Err` 值给调用代码。同样的事情也适用于 `read_to_string` 调用末尾的 `?` 操作符。
|
||||
|
||||
`?` 操作符消除了大量模板代码,而使这个函数的实现更简单。我们甚至可以通过在 `?` 后立即链接方法调用,进一步缩短这段代码,如下清单 9-8 中所示。
|
||||
|
||||
|
||||
<a name="listing_9-8"></a>
|
||||
文件名:`src/main.rs`
|
||||
|
||||
```rust
|
||||
@@ -291,39 +277,34 @@ fn read_username_from_file() -> Result<String, io::Error> {
|
||||
}
|
||||
```
|
||||
|
||||
*清单 9-8:在 `?` 操作符后链接方法调用*
|
||||
**清单 9-8**:在 `?` 操作符后链接方法调用
|
||||
|
||||
|
||||
我们已将 `username` 中那个新 `String` 的创建,移至该函数的开头;这部分未曾改变。与创建变量 `username_file` 相反,我们已将到 `read_to_string` 的调用,直接链接到 `File::open("hello.txt")?` 的结果上。在 `read_to_string` 调用末尾,我们仍然有个 `?`,而在 `File::open` 及 `read_to_string` 两个调用都成功时,我们仍会返回一个包含着 `username` 的 `Ok` 值,而不是返回错误。清单 9-6 和清单 9-7 中的功能又一次相同;这只是编写他的一种不同的、更符合人体工程学的方式。
|
||||
我们已移动 `username` 中新的 `String` 的创建至函数的开头;这部分未曾改变。与其创建变量 `username_file`,我们已直接链接到 `read_to_string` 的调用到 `File::open("hello.txt")?` 的结果上。在 `read_to_string` 调用末尾我们仍然有个 `?`,而在 `File::open` 与 `read_to_string` 两个调用都成功后,我们仍返回一个包含 `username` 的 `Ok` 值,而不是返回错误。功能再度与清单 9-6 和清单 9-7 中的相同;这只是编写他的一种不同的、更符合人体工程学的方式。
|
||||
|
||||
下面清单 9-9 展示了一种使用 `fs::read_to_string`,使其更简短的方法。
|
||||
下面清单 9-9 展示了一种使用 `fs::read_to_string` 使其更简短的方式。
|
||||
|
||||
<a name="listing_9-9"></a>
|
||||
文件名:`src/main.rs`
|
||||
|
||||
```rust
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::{fs, io};
|
||||
|
||||
fn read_username_from_file() -> Result<String, io::Error> {
|
||||
fs::read_to_string("hello.txt")
|
||||
}
|
||||
```
|
||||
|
||||
*清单 9-9:使用 `fs::read_to_string` 而非打开然后读取文件*
|
||||
**清单 9-9**:使用 `fs::read_to_string` 而不是打开然后读取文件
|
||||
|
||||
读取文件到字符串中属于一个相当常见的操作,因此标准库提供了便捷的 `fs::read_to_string` 函数,其会打开文件、创建一个新的 `String`、读取文件内容、放入该内容到 `String` 中,并将其返回。当然,使用 `fs::read_too_string` 函数不会给予我们解释所有错误处理的机会,所以我们先以更长的方式实现了他。
|
||||
|
||||
|
||||
将某个文件读入字符串,是种相当常见的操作,因此标准库提供了可以打开该文件、创建出一个新 `String`、读取该文件内容、将内容放入这个 `String`,并将其返回的便利的 `fs::read_too_string` 函数。当然,使用 `fs::read_too_string` 函数并并未给到我们解释所有这些错误处理的机会,所以我们先以更长的方式实现。
|
||||
|
||||
|
||||
|
||||
### `?` 操作符可用于哪些地方
|
||||
|
||||
**Where The `?` Operator Can Be Used**
|
||||
|
||||
### 哪些地方要使用 `?` 操作符?
|
||||
|
||||
`?` 操作符只能用在那些返回类型与 `?` 所用在值兼容的那些函数中。这是因为 `?` 操作符被定义为以与我们在清单 9-6 中定义的 `match` 表达式相同方式,执行一次从函数提前返回某个值。在清单 9-6 中,`match` 表达式使用了个 `Result` 值,并提前返回那个返回了个 `Err(e)` 值的支臂。该函数的返回类型,必须是个 `Result`,这样才能与这个 `return` 语句兼容。
|
||||
|
||||
在下面的清单 9-10 中,我们来看看若咱们在某个 `main` 函数中,与某个跟咱们在其上使用 `?` 的值不兼容返回类型,一起使用 `?` 运算符时,会得到一个什么样的报错。
|
||||
在下面的清单 9-10 中,我们来看看若咱们在某个 `main` 函数中,与某个跟咱们在其上使用 `?` 的值不兼容返回类型,一起使用 `?` 操作符时,会得到一个什么样的报错。
|
||||
|
||||
文件名:`src/mian.rs`
|
||||
|
||||
@@ -338,7 +319,7 @@ fn main() {
|
||||
*清单 9-10:尝试在返回 `()` 的 `main` 函数中使用 `?` 将不编译*
|
||||
|
||||
|
||||
这段代码打开某个文件,但这可能会失败。运算符 `?` 跟在由 `File::open` 返回的一个 `Result` 值后,但这个 `main` 函数有着返回类型 `()`,而非 `Result`。当我们编译这段代码时,我们会得到以下错误消息:
|
||||
这段代码打开某个文件,但这可能会失败。操作符 `?` 跟在由 `File::open` 返回的一个 `Result` 值后,但这个 `main` 函数有着返回类型 `()`,而非 `Result`。当我们编译这段代码时,我们会得到以下错误消息:
|
||||
|
||||
```console
|
||||
$ cargo run
|
||||
@@ -366,11 +347,11 @@ error: could not compile `result_demo` (bin "result_demo") due to 1 previous err
|
||||
|
||||
要修复这个错误,咱们有两个选择:
|
||||
|
||||
- 一种选择是将咱们函数的返回类型,修改为与咱们在其上使用 `?` 运算符的值兼容,只要咱们没有阻止这样做的限制;
|
||||
- 一种选择是将咱们函数的返回类型,修改为与咱们在其上使用 `?` 操作符的值兼容,只要咱们没有阻止这样做的限制;
|
||||
- 另一选择是使用 `match` 表达式,或 `Result<T, E>` 的方法之一,以任何适当的方式处理这个 `Result<T,E>`。
|
||||
|
||||
|
||||
报错消息还提到,`?` 也可与 `Option<T>` 的值一起使用。与对 `Result` 使用 `?` 运算符一样,咱们只能在返回 `Option` 的函数中,于 `Option` 上使用 `?` 运算符。在某个 `Option<T>` 上调用 `?` 操作符的行为,与在某个 `Result<T, E>` 上调用 `?` 操作符的行为类似:在值为 `None` 时,这个 `None` 就将在此时从该函数提前返回。在值为 `Some`,`Some` 中的值就是该表达式的结果值,同时该函数会继续执行。下面清单 9-11 有着一个查找给定文本中第一行最后一个字符的函数示例。
|
||||
报错消息还提到,`?` 也可与 `Option<T>` 的值一起使用。与对 `Result` 使用 `?` 操作符一样,咱们只能在返回 `Option` 的函数中,于 `Option` 上使用 `?` 操作符。在某个 `Option<T>` 上调用 `?` 操作符的行为,与在某个 `Result<T, E>` 上调用 `?` 操作符的行为类似:在值为 `None` 时,这个 `None` 就将在此时从该函数提前返回。在值为 `Some`,`Some` 中的值就是该表达式的结果值,同时该函数会继续执行。下面清单 9-11 有着一个查找给定文本中第一行最后一个字符的函数示例。
|
||||
|
||||
|
||||
```rust
|
||||
|
||||
Reference in New Issue
Block a user