diff --git a/src/ch09-01-unrecoverable-errors-with-panic.md b/src/ch09-01-unrecoverable-errors-with-panic.md index 1313439..0981edf 100644 --- a/src/ch09-01-unrecoverable-errors-with-panic.md +++ b/src/ch09-01-unrecoverable-errors-with-panic.md @@ -2,13 +2,15 @@ > [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/main/src/ch09-01-unrecoverable-errors-with-panic.md) >
-> commit 199ca99926f232ee7f581a917eada4b65ff21754 +> commit 2921743516b3e2c0f45a95390e7b536e42f4af7c -突然有一天,代码出问题了,而你对此束手无策。对于这种情况,Rust 有 `panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug,而且程序员并不清楚该如何处理它。 +突然有一天,代码出问题了,而你对此束手无策。对于这种情况,Rust 有 `panic!`宏。在实践中有两种方法造成 panic:执行会造成代码 panic 的操作(比如访问超过数组结尾的内容)或者显式调用 `panic!` 宏。这两种情况都会使程序 panic。通常情况下这些 panic 会打印出一个错误信息,展开并清理栈数据,然后退出。通过一个环境变量,你也可以让 Rust 在 panic 发生时打印调用堆栈(call stack)以便于定位 panic 的原因。 > ### 对应 panic 时的栈展开或终止 > -> 当出现 panic 时,程序默认会开始 **展开**(*unwinding*),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 **终止**(*abort*),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,panic 时通过在 *Cargo.toml* 的 `[profile]` 部分增加 `panic = 'abort'`,可以由展开切换为终止。例如,如果你想要在 release 模式中 panic 时直接终止: +> 当出现 panic 时,程序默认会开始 **展开**(*unwinding*),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 **终止**(*abort*),这会不清理数据就退出程序。 +> +> 那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,panic 时通过在 *Cargo.toml* 的 `[profile]` 部分增加 `panic = 'abort'`,可以由展开切换为终止。例如,如果你想要在 release 模式中 panic 时直接终止: > > ```toml > [profile.release] @@ -62,21 +64,21 @@ $ RUST_BACKTRACE=1 cargo run thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5 stack backtrace: 0: rust_begin_unwind - at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:483 + at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5 1: core::panicking::panic_fmt - at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:85 + at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14 2: core::panicking::panic_bounds_check - at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:62 + at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5 3: >::index - at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:255 + at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10 4: core::slice::index:: for [T]>::index - at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:15 - 5: as core::ops::index::Index>::index - at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/alloc/src/vec.rs:1982 + at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9 + 5: as core::ops::index::Index>::index + at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9 6: panic::main - at ./src/main.rs:4 + at ./src/main.rs:4:5 7: core::ops::function::FnOnce::call_once - at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/function.rs:227 + at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. ``` diff --git a/src/ch09-02-recoverable-errors-with-result.md b/src/ch09-02-recoverable-errors-with-result.md index 456c362..24028a8 100644 --- a/src/ch09-02-recoverable-errors-with-result.md +++ b/src/ch09-02-recoverable-errors-with-result.md @@ -2,7 +2,7 @@ > [ch09-02-recoverable-errors-with-result.md](https://github.com/rust-lang/book/blob/main/src/ch09-02-recoverable-errors-with-result.md) >
-> commit 0bac27c66136764c82fe267763945f3c65eea002 +> commit 699adc6f5cb76f6e9d567ff0a57d8a844ac07a88 大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应的原因失败。例如,如果因为打开一个并不存在的文件而失败,此时我们可能想要创建这个文件,而不是终止进程。 @@ -27,23 +27,9 @@ enum Result { 示例 9-3:打开文件 -如何知道 `File::open` 返回一个 `Result` 呢?我们可以查看 [标准库 API 文档](https://doc.rust-lang.org/std/index.html),或者可以直接问编译器!如果给 `f` 某个我们知道 **不是** 函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们 `f` 的类型 **应该** 是什么。让我们试试!我们知道 `File::open` 的返回值不是 `u32` 类型的,所以将 `let f` 语句改为如下: +`File::open` 的返回值是 `Result`。泛型参数 `T` 会被 `File::open` 的实现放入成功返回值的类型 `std::fs::File`,这是一个文件句柄。错误返回值使用的 `E` 的类型是 `std::io::Error`。这些返回类型意味着 `File::open` 调用可能成功并返回一个可以读写的文件句柄。这个函数调用也可能会失败:例如,也许文件不存在,或者可能没有权限访问这个文件。`File::open` 函数需要一个方法在告诉我们成功与否的同时返回文件句柄或者错误信息。这些信息正好是 `Result` 枚举所代表的。 -```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch09-error-handling/no-listing-02-ask-compiler-for-type/src/main.rs:here}} -``` - -现在尝试编译会给出如下输出: - -```console -{{#include ../listings/ch09-error-handling/no-listing-02-ask-compiler-for-type/output.txt}} -``` - -这就告诉我们了 `File::open` 函数的返回值类型是 `Result`。这里泛型参数 `T` 放入了成功值的类型 `std::fs::File`,它是一个文件句柄。`E` 被用在失败值上时 `E` 的类型是 `std::io::Error`。 - -这个返回值类型说明 `File::open` 调用可能会成功并返回一个可以进行读写的文件句柄。这个函数也可能会失败:例如,文件可能并不存在,或者可能没有访问文件的权限。`File::open` 需要一个方式告诉我们是成功还是失败,并同时提供给我们文件句柄或错误信息。而这些信息正是 `Result` 枚举可以提供的。 - -当 `File::open` 成功的情况下,变量 `f` 的值将会是一个包含文件句柄的 `Ok` 实例。在失败的情况下,`f` 的值会是一个包含更多关于出现了何种错误信息的 `Err` 实例。 +当 `File::open` 成功时,`greeting_file_result` 变量将会是一个包含文件句柄的 `Ok` 实例。当失败时,`greeting_file_result` 变量将会是一个包含了更多关于发生了何种错误的信息的 `Err` 实例。 我们需要在示例 9-3 的代码中增加根据 `File::open` 返回值进行不同处理的逻辑。示例 9-4 展示了一个使用基本工具处理 `Result` 的例子:第六章学习过的 `match` 表达式。 @@ -57,7 +43,7 @@ enum Result { 注意与 `Option` 枚举一样,`Result` 枚举和其成员也被导入到了 prelude 中,所以就不需要在 `match` 分支中的 `Ok` 和 `Err` 之前指定 `Result::`。 -这里我们告诉 Rust 当结果是 `Ok` 时,返回 `Ok` 成员中的 `file` 值,然后将这个文件句柄赋值给变量 `f`。`match` 之后,我们可以利用这个文件句柄来进行读写。 +这里我们告诉 Rust 当结果是 `Ok` 时,返回 `Ok` 成员中的 `file` 值,然后将这个文件句柄赋值给变量 `greeting_file`。`match` 之后,我们可以利用这个文件句柄来进行读写。 `match` 的另一个分支处理从 `File::open` 得到 `Err` 值的情况。在这种情况下,我们选择调用 `panic!` 宏。如果当前目录没有一个叫做 *hello.txt* 的文件,当运行这段代码时会看到如下来自 `panic!` 宏的输出: @@ -65,7 +51,6 @@ enum Result { {{#include ../listings/ch09-error-handling/listing-09-04/output.txt}} ``` - 一如既往,此输出准确地告诉了我们到底出了什么错。 ### 匹配不同的错误 @@ -80,7 +65,7 @@ enum Result { 示例 9-5:使用不同的方式处理不同类型的错误 -`File::open` 返回的 `Err` 成员中的值类型 `io::Error`,它是一个标准库中提供的结构体。这个结构体有一个返回 `io::ErrorKind` 值的 `kind` 方法可供调用。`io::ErrorKind` 是一个标准库提供的枚举,它的成员对应 `io` 操作可能导致的不同错误类型。我们感兴趣的成员是 `ErrorKind::NotFound`,它代表尝试打开的文件并不存在。这样,`match` 就匹配完 `f` 了,不过对于 `error.kind()` 还有一个内层 `match`。 +`File::open` 返回的 `Err` 成员中的值类型 `io::Error`,它是一个标准库中提供的结构体。这个结构体有一个返回 `io::ErrorKind` 值的 `kind` 方法可供调用。`io::ErrorKind` 是一个标准库提供的枚举,它的成员对应 `io` 操作可能导致的不同错误类型。我们感兴趣的成员是 `ErrorKind::NotFound`,它代表尝试打开的文件并不存在。这样,`match` 就匹配完 `greeting_file_result` 了,不过对于 `error.kind()` 还有一个内层 `match`。 我们希望在内层 `match` 中检查的条件是 `error.kind()` 的返回值是否为 `ErrorKind`的 `NotFound` 成员。如果是,则尝试通过 `File::create` 创建文件。然而因为 `File::create` 也可能会失败,还需要增加一个内层 `match` 语句。当文件不能被打开,会打印出一个不同的错误信息。外层 `match` 的最后一个分支保持不变,这样对任何除了文件不存在的错误会使程序 panic。 @@ -95,7 +80,7 @@ enum Result { > use std::io::ErrorKind; > > fn main() { -> let f = File::open("hello.txt").unwrap_or_else(|error| { +> 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); @@ -122,9 +107,9 @@ enum Result { 如果调用这段代码时不存在 *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" } }', -src/libcore/result.rs:906:4 +thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { +code: 2, kind: NotFound, message: "No such file or directory" }', +src/main.rs:4:49 ``` 还有另一个类似于 `unwrap` 的方法它还允许我们选择 `panic!` 的错误信息:`expect`。使用 `expect` 而不是 `unwrap` 并提供一个好的错误信息可以表明你的意图并更易于追踪 panic 的根源。`expect` 的语法看起来像这样: @@ -138,11 +123,12 @@ src/libcore/result.rs:906:4 `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" } }', src/libcore/result.rs:906:4 +thread 'main' panicked at 'hello.txt should be included in this project: Error +{ repr: Os { code: 2, message: "No such file or directory" } }', +src/libcore/result.rs:906:4 ``` -因为这个错误信息以我们指定的文本开始,`Failed to open hello.txt`,将会更容易找到代码中的错误信息来自何处。如果在多处使用 `unwrap`,则需要花更多的时间来分析到底是哪一个 `unwrap` 造成了 panic,因为所有的 `unwrap` 调用都打印相同的信息。 +在生产级别的代码中,大部分 Rustaceans 选择 `expect` 而不是 `unwrap` 并提供更多关于为何操作期望是一直成功的上下文。如此如果该假设真的被证明是错的,你也有更多的信息来用于调试。 ### 传播错误 @@ -158,11 +144,13 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code: 示例 9-6:一个函数使用 `match` 将错误返回给代码调用者 -首先让我们看看函数的返回值:`Result`。这意味着函数返回一个 `Result` 类型的值,其中泛型参数 `T` 的具体类型是 `String`,而 `E` 的具体类型是 `io::Error`。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含 `String` 的 `Ok` 值 —— 函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个 `Err` 值,它储存了一个包含更多这个问题相关信息的 `io::Error` 实例。这里选择 `io::Error` 作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:`File::open` 函数和 `read_to_string` 方法。 +这个函数可以编写成更加简短的形式,不过我们以大量手动处理开始以便探索错误处理;在最后我们会展示更短的形式。让我们看看函数的返回值:`Result`。这意味着函数返回一个 `Result` 类型的值,其中泛型参数 `T` 的具体类型是 `String`,而 `E` 的具体类型是 `io::Error`。 -函数体以调用 `File::open` 函数开始。接着使用 `match` 处理返回值 `Result`,类似示例 9-4,如果 `File::open` 成功了,模式变量 `file` 中的文件句柄就变成了可变变量 `f` 中的值,接着函数继续执行。在 `Err` 的情况下,我们没有调用 `panic!`,而是使用 `return` 关键字提前结束整个函数,并将来自 `File::open` 的错误值(现在在模式变量 `e` 中)作为函数的错误值传回给调用者。 +如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含 `String` 的 `Ok` 值 —— 函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个 `Err` 值,它储存了一个包含更多这个问题相关信息的 `io::Error` 实例。这里选择 `io::Error` 作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:`File::open` 函数和 `read_to_string` 方法。 -所以 `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`,因为这是函数的最后一个表达式。 +函数体以调用 `File::open` 函数开始。接着使用 `match` 处理返回值 `Result`,类似示例 9-4,如果 `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!` 并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适的处理方法。 @@ -182,9 +170,11 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code: `Result` 值之后的 `?` 被定义为与示例 9-6 中定义的处理 `Result` 值的 `match` 表达式有着完全相同的工作方式。如果 `Result` 的值是 `Ok`,这个表达式将会返回 `Ok` 中的值而程序将继续执行。如果值是 `Err`,`Err` 中的值将作为整个函数的返回值,就好像使用了 `return` 关键字一样,这样错误值就被传播给了调用者。 -示例 9-6 中的 `match` 表达式与问号运算符所做的有一点不同:`?` 运算符所使用的错误值被传递给了 `from` 函数,它定义于标准库的 `From` trait 中,其用来将错误从一种类型转换为另一种类型。当 `?` 运算符调用 `from` 函数时,收到的错误类型被转换为由当前函数返回类型所指定的错误类型。这在当函数返回单个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。只要每一个错误类型都实现了 `from` 函数来定义如何将自身转换为返回的错误类型,`?` 运算符会自动处理这些转换。 +示例 9-6 中的 `match` 表达式与 `?` 运算符所做的有一点不同:`?` 运算符所使用的错误值被传递给了 `from` 函数,它定义于标准库的 `From` trait 中,其用来将错误从一种类型转换为另一种类型。当 `?` 运算符调用 `from` 函数时,收到的错误类型被转换为由当前函数返回类型所指定的错误类型。这在当函数返回单个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。 + +例如,我们可以将示例 9-7 中的 `read_username_from_file` 函数修改为返回一个自定义的 `OurError` 错误类型。如果我们也定义了 `impl From for OurError` 来从 `io::Error` 构造一个 `OurError` 实例,那么 `read_username_from_file` 函数体中的 `?` 运算符调用会调用 `from` 并转换错误而无需在函数中增加任何额外的代码。 -在示例 9-7 的上下文中,`File::open` 调用结尾的 `?` 将会把 `Ok` 中的值返回给变量 `f`。如果出现了错误,`?` 运算符会提早返回整个函数并将一些 `Err` 值传播给调用者。同理也适用于 `read_to_string` 调用结尾的 `?`。 +在示例 9-7 的上下文中,`File::open` 调用结尾的 `?` 会将 `Ok` 中的值返回给变量 `username_file`。如果发生了错误,`?` 运算符会使整个函数提前返回并将任何 `Err` 值返回给调用代码。同理也适用于 `read_to_string` 调用结尾的 `?`。 `?` 运算符消除了大量样板代码并使得函数的实现更简单。我们甚至可以在 `?` 之后直接使用链式方法调用来进一步缩短代码,如示例 9-8 所示: @@ -196,9 +186,9 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code: 示例 9-8:问号运算符之后的链式方法调用 -在 `s` 中创建新的 `String` 被放到了函数开头;这一部分没有变化。我们对 `File::open("hello.txt")?` 的结果直接链式调用了 `read_to_string`,而不再创建变量 `f`。仍然需要 `read_to_string` 调用结尾的 `?`,而且当 `File::open` 和 `read_to_string` 都成功没有失败时返回包含用户名 `s` 的 `Ok` 值。其功能再一次与示例 9-6 和示例 9-7 保持一致,不过这是一个与众不同且更符合工程学(ergonomic)的写法。 +在 `username` 中创建新的 `String` 被放到了函数开头;这一部分没有变化。我们对 `File::open("hello.txt")?` 的结果直接链式调用了 `read_to_string`,而不再创建变量 `username_file`。仍然需要 `read_to_string` 调用结尾的 `?`,而且当 `File::open` 和 `read_to_string` 都成功没有失败时返回包含用户名 `username` 的 `Ok` 值。其功能再一次与示例 9-6 和示例 9-7 保持一致,不过这是一个与众不同且更符合工程学(ergonomic)的写法。 -说到编写这个函数的不同方法,甚至还有一个更短的写法: +示例 9-9 展示了一个使用 `fs::read_to_string` 的更为简短的写法: 文件名:src/main.rs @@ -206,7 +196,7 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code: {{#include ../listings/ch09-error-handling/listing-09-09/src/main.rs:here}} ``` -示例 9-9: 使用 `fs::read_to_string` +示例 9-9: 使用 `fs::read_to_string` 而不是打开后读取文件 将文件读取到一个字符串是相当常见的操作,所以 Rust 提供了名为 `fs::read_to_string` 的函数,它会打开文件、新建一个 `String`、读取文件的内容,并将内容放入 `String`,接着返回它。当然,这样做就没有展示所有这些错误处理的机会了,所以我们最初就选择了艰苦的道路。 @@ -216,6 +206,8 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code: 在示例 9-10 中,让我们看看在返回值不兼容的 `main` 函数中使用 `?` 运算符会得到什么错误: +文件名:src/main.rs + ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch09-error-handling/listing-09-10/src/main.rs}} ``` @@ -228,13 +220,16 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code: {{#include ../listings/ch09-error-handling/listing-09-10/output.txt}} ``` -这个错误指出只能在返回 `Result` 或者其它实现了 `FromResidual` 的类型的函数中使用 `?` 运算符。为了修复这个错误,有两个选择。一个是,如果没有限制的话将函数的返回值改为 `Result`。另一个是使用 `match` 或 `Result` 的方法中合适的一个来处理 `Result`。 +这个错误指出只能在返回 `Result` 或者其它实现了 `FromResidual` 的类型的函数中使用 `?` 运算符。 + +为了修复这个错误,有两个选择。一个是,如果没有限制的话将函数的返回值改为 `Result`。另一个是使用 `match` 或 `Result` 的方法中合适的一个来处理 `Result`。 错误信息也提到 `?` 也可用于 `Option` 值。如同对 `Result` 使用 `?` 一样,只能在返回 `Option` 的函数中对 `Option` 使用 `?`。在 `Option` 上调用 `?` 运算符的行为与 `Result` 类似:如果值是 `None`,此时 `None` 会从函数中提前返回。如果值是 `Some`,`Some` 中的值作为表达式的返回值同时函数继续。示例 9-11 中有一个从给定文本中返回第一行最后一个字符的函数的例子: ```rust {{#rustdoc_include ../listings/ch09-error-handling/listing-09-11/src/main.rs:here}} ``` + 示例 9-11: 在 `Option` 值上使用 `?` 运算符 这个函数返回 `Option` 因为它可能会在这个位置找到一个字符,也可能没有字符。这段代码获取 `text` 字符串 slice 作为参数并调用其 `lines` 方法,这会返回一个字符串中每一行的迭代器。因为函数希望检查第一行,所以调用了迭代器 `next` 来获取迭代器中第一个值。如果 `text` 是空字符串,`next` 调用会返回 `None`,此时我们可以使用 `?` 来停止并从 `last_char_of_first_line` 返回 `None`。如果 `text` 不是空字符串,`next` 会返回一个包含 `text` 中第一行的字符串 slice 的 `Some` 值。 @@ -253,11 +248,11 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code: 示例 9-12: 修改 `main` 返回 `Result<(), E>` 允许对 `Result` 值使用 `?` 运算符 -`Box` 类型是一个 **trait 对象**(*trait object*)第十七章 [“为使用不同类型的值而设计的 trait 对象”][trait-objects] 部分会做介绍。目前可以将 `Box` 理解为 “任何类型的错误”。在返回 `Box` 错误类型 `main` 函数中对 `Result` 使用 `?` 是允许的,因为它允许任何 `Err` 值提前返回。 +`Box` 类型是一个 **trait 对象**(*trait object*)第十七章 [“为使用不同类型的值而设计的 trait 对象”][trait-objects] 部分会做介绍。目前可以将 `Box` 理解为 “任何类型的错误”。在返回 `Box` 错误类型 `main` 函数中对 `Result` 使用 `?` 是允许的,因为它允许任何 `Err` 值提前返回。即便 `main` 函数体从来只会返回 `std::io::Error` 错误类型,通过指定 `Box`,这个签名也仍是正确的,甚至当 `main` 函数体中增加更多返回其他错误类型的代码时也是如此。 当 `main` 函数返回 `Result<(), E>`,如果 `main` 返回 `Ok(())` 可执行程序会以 `0` 值退出,而如果 `main` 返回 `Err` 值则会以非零值退出;成功退出的程序会返回整数 `0`,运行错误的程序会返回非 `0` 的整数。Rust 也会从二进制程序中返回与这个惯例相兼容的整数。 -`main` 函数也可以返回任何实现了 [`std::process::Termination` trait][termination] 的类型。截至本书编写时,`Termination` trait 是一个不稳定功能(unstable feature),只能用于 Nightly Rust 中,所以你不能在 稳定版 Rust(Stable Rust)中用自己的类型去实现,不过有朝一日应该可以! +`main` 函数也可以返回任何实现了 [`std::process::Termination` trait][termination] 的类型,它包含了一个返回 `ExitCode` 的 `report` 函数。请查阅标准库文档了解更多为自定义类型实现 `Termination` trait 的细节。 现在我们讨论过了调用 `panic!` 或返回 `Result` 的细节,是时候回到他们各自适合哪些场景的话题了。