check ch10-01

This commit is contained in:
KaiserY 2017-08-21 08:33:53 +08:00
parent c5a42db0bf
commit 617ece0b98
7 changed files with 179 additions and 196 deletions

View File

@ -8,4 +8,4 @@ Rust 对可靠性的执着也扩展到了错误处理。错误对于软件来说
Rust 将错误组合成两个主要类别:**可恢复错误***recoverable*)和 **不可恢复错误***unrecoverable*)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。
大部分语言并不区分这两类错误并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有`Result<T, E>`值和`panic!`,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍`panic!`调用,接着会讲到如何返回`Result<T, E>`。最后,我们会讨论当决定是尝试从错误中恢复还是停止执行时需要顾及的权衡考虑。
大部分语言并不区分这两类错误并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有 `Result<T, E>` 值,以及 `panic!`,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍 `panic!` 调用,接着会讲到如何返回 `Result<T, E>`。最后,我们会讨论当决定是尝试从错误中恢复还是停止执行时需要顾及的权衡考虑。

View File

@ -2,7 +2,7 @@
> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-01-unrecoverable-errors-with-panic.md)
> <br>
> commit e26bb338ab14b98a850c3464e821d54940a45672
> commit 8d24b2a5e61b4eea109d26e38d2144408ae44e53
突然有一天糟糕的事情发生了而你对此束手无策。对于这种情况Rust 有 `panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
@ -17,7 +17,7 @@
让我们在一个简单的程序中调用 `panic!`
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust,should_panic
fn main() {
@ -27,10 +27,10 @@ fn main() {
运行程序将会出现类似这样的输出:
```
```text
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished debug [unoptimized + debuginfo] target(s) in 0.25 secs
Finished dev [unoptimized + debuginfo] target(s) in 0.25 secs
Running `target/debug/panic`
thread 'main' panicked at 'crash and burn', src/main.rs:2
note: Run with `RUST_BACKTRACE=1` for a backtrace.
@ -45,7 +45,7 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
让我们来看看另一个因为我们代码中的 bug 引起的别的库中 `panic!` 的例子,而不是直接的宏调用:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust,should_panic
fn main() {
@ -61,10 +61,10 @@ fn main() {
为了使程序远离这类漏洞如果尝试读取一个索引不存在的元素Rust 会停止执行并拒绝继续。尝试运行上面的程序会出现如下:
```
```text
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished debug [unoptimized + debuginfo] target(s) in 0.27 secs
Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
100', /stable-dist-rustc/build/src/libcollections/vec.rs:1362
@ -76,9 +76,9 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
接下来的几行提醒我们可以设置 `RUST_BACKTRACE` 环境变量来得到一个 backtrace 来调查究竟是什么导致了错误。让我们来试试看。列表 9-1 显示了其输出:
```
```text
$ RUST_BACKTRACE=1 cargo run
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', /stable-dist-rustc/build/src/libcollections/vec.rs:1392
stack backtrace:
@ -116,11 +116,10 @@ stack backtrace:
17: 0x0 - <unknown>
```
<span class="caption">Listing 9-1: The backtrace generated by a call to
`panic!` displayed when the environment variable `RUST_BACKTRACE` is set</span>
<span class="caption">列表 9-1当设置 `RUST_BACKTRACE` 环境变量时 `panic!` 调用所生成的 backtrace 信息</span>
这里有大量的输出backtrace 第 11 行指向了我们程序中引起错误的行:*src/main.rs* 的第四行。backtrace 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。
如果你不希望我们的程序 panic第一个提到我们编写的代码行的位置是你应该开始调查的以便查明是什么值如何在这个地方引起了 panic。在上面的例子中我们故意编写会 panic 的代码来演示如何使用 backtrace修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你代码出现了 panic你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic以及应当如何处理才能避免这个问题。
如果你不希望我们的程序 panic第一个提到我们编写的代码行的位置是你应该开始调查的以便查明是什么值如何在这个地方引起了 panic。在上面的例子中我们故意编写会 panic 的代码来演示如何使用 backtrace修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你代码出现了 panic你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic以及应当如何处理才能避免这个问题。
本章的后面会再次回到 `panic!` 并讲到何时应该何时不应该使用这个方式。接下来,我们来看看如何使用 `Result` 来从错误中恢复。

View File

@ -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
-->
|

View File

@ -2,7 +2,7 @@
> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-03-to-panic-or-not-to-panic.md)
> <br>
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
> commit 88dee413c0792dcaf563d753e00400cc191f1fbe
那么,该如何决定何时应该 `panic!` 以及何时应该返回 `Result` 呢?如果代码 panic就没有恢复的可能。你可以选择对任何错误场景都调用 `panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回 `Result` 值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为 `Err` 是不可恢复的,所以他们也可能会调用 `panic!` 并将可恢复的错误变成了不可恢复的错误。因此返回 `Result` 是定义可能会失败的函数的一个好的默认选择。
@ -34,19 +34,19 @@ let home = "127.0.0.1".parse::<IpAddr>().unwrap();
* 有害状态并不包含 **预期** 会偶尔发生的错误
* 之后的代码的运行依赖于不再处于这种有害状态
* 当没有可行的手段来将有害状态信息编码进可用的类型中(并返回)的情况
* 当没有可行的手段来将有害状态信息编码进所使用的类型中的情况
如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是 `panic!` 并警告使用你的库的人他的代码中有 bug 以便他能在开发时就修复它。类似的,`panic!` 通常适合调用不能够控制的外部代码时,这时无法修复其返回的无效状态。
无论代码编写的多么好,当有害状态是预期会出现时,返回 `Result` 仍要比调用 `panic!` 更为合适。这样的例子包括解析器接收到错误数据,或者 HTTP 请求返回一个表明触发了限流的状态。在这些例子中,应该通过返回 `Result` 来表明失败预期是可能的,这样将有害状态向上传播,这样调用者就可以决定该如何处理这个问题。使用 `panic!` 来处理这些情况就不是最好的选择。
当代码对值进行操作时,应该首先验证值是有效的,并在其无效时`panic!`。这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会`panic!`的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。函数通常都遵循**契约***contracts*):他们的行为只有在输入满足特定条件时才能得到保证。当违反契约时 panic 是有道理的,因为这这通常代表调用方的 bug而且这也不是那种你希望必须处理的错误。事实上也没有合理的方式来恢复调用方的代码:调用方的**程序员**需要修复他的代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中得到解释。
当代码对值进行操作时,应该首先验证值是有效的,并在其无效时 `panic!`。这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。函数通常都遵循 **契约***contracts*):他们的行为只有在输入满足特定条件时才能得到保证。当违反契约时 panic 是有道理的,因为这这通常代表调用方的 bug而且这也不是那种你希望调用方必须处理的错误。事实上也没有合理的方式来恢复调用方的代码:调用方的 **程序员** 需要修复他的代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中得到解释。
虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个不同于`Option`的类型,而且程序期望它是**有值**的而不是**空值**。你的代码无需处理`Some`和`None`这两种情况,它只会有一种情况且绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像`u32`这样的无符号整型,也会确保它永远不为负。
虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个不同于 `Option` 的类型,而且程序期望它是 **有值** 的并且不是 **空值**。你的代码无需处理 `Some``None` 这两种情况,它只会有一种情况就是绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像 `u32` 这样的无符号整型,也会确保它永远不为负。
### 创建自定义类型作为验证
让我们借用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型作为验证。回忆一下第二章的猜猜看游戏,它的代码请求用户猜测一个 1 到 100 之间的数字在将其与秘密数字做比较之前我们事实上从未验证用户的猜测是位于这两个数字之间的只保证它为正。在当前情况下其影响并不是很严重“Too high”或“Too low”的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助例如当用户猜测一个超出范围的数字输入字母时采取不同的行为。
让我们借用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型作为验证。回忆一下第二章的猜猜看游戏,它的代码请求用户猜测一个 1 到 100 之间的数字在将其与秘密数字做比较之前我们事实上从未验证用户的猜测是位于这两个数字之间的只保证它为正。在当前情况下其影响并不是很严重“Too high” “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。
一种实现方式是将猜测解析成 `i32` 而不仅仅是 `u32`,来默许输入负数,接着检查数字是否在范围内:
@ -76,7 +76,7 @@ loop {
相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全的在函数签名中使用新类型并相信他们接收到的值。列表 9-8 中展示了一个定义 `Guess` 类型的方法,只有在 `new` 函数接收到 1 到 100 之间的值时才会创建 `Guess` 的实例:
```rust
struct Guess {
pub struct Guess {
value: u32,
}
@ -87,7 +87,7 @@ impl Guess {
}
Guess {
value: value,
value
}
}
@ -97,8 +97,7 @@ impl Guess {
}
```
<span class="caption">Listing 9-8: A `Guess` type that will only continue with
values between 1 and 100</span>
<span class="caption">列表 9-8一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续</span>
首先,我们定义了一个包含 `u32` 类型字段 `value` 的结构体 `Guess`。这里是储存猜测值的地方。

View File

@ -2,7 +2,7 @@
> [ch10-00-generics.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-00-generics.md)
> <br>
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
> commit f65676e17d7fc4c0c7cd7275a7bf15447364831a
每一个编程语言都有高效的处理重复概念的工具;在 Rust 中工具之一就是 **泛型***generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。
@ -10,9 +10,9 @@
首先,我们将回顾一下提取函数以减少代码重复的机制。接着使用一个只在参数类型上不同的泛型函数来实现相同的功能。我们也会讲到结构体和枚举定义中的泛型。
之后,我们讨论 *traits*这是一个定义泛型行为的方法。trait 可以与泛型结合来将泛型限制为拥有特定行为的类型,而不是任意类型。
之后,我们讨论 *trait*这是一个定义泛型行为的方法。trait 可以与泛型结合来将泛型限制为拥有特定行为的类型,而不是任意类型。
最后介绍**生命周期***lifetimes*它是一类允许我们向编译器提供引用如何相互关联的泛型。Rust 的生命周期功能允许在很多场景下借用值同时仍然使编译器能够检查这些引用的有效性。
最后介绍 **生命周期***lifetimes*它是一类允许我们向编译器提供引用如何相互关联的泛型。Rust 的生命周期功能允许在很多场景下借用值同时仍然使编译器能够检查这些引用的有效性。
## 提取函数来减少重复
@ -20,15 +20,15 @@
考虑一下这个寻找列表中最大值的小程序,如列表 10-1 所示:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = numbers[0];
let mut largest = number_list[0];
for number in numbers {
for number in number_list {
if number > largest {
largest = number;
}
@ -39,22 +39,21 @@ fn main() {
}
```
<span class="caption">Listing 10-1: Code to find the largest number in a list
of numbers</span>
<span class="caption">列表 10-1在一个数字列表中寻找最大值的函数</span>
这段代码获取一个整型列表,存放在变量`numbers`中。它将列表的第一项放入了变量`largest`中。接着遍历了列表中的所有数字,如果当前值大于`largest`中储存的值,将`largest`替换为这个值。如果当前值小于目前为止的最大值,`largest`保持不变。当列表中所有值都被考虑到之后,`largest`将会是最大值,在这里也就是 100。
这段代码获取一个整型列表,存放在变量 `number_list` 中。它将列表的第一项放入了变量 `largest` 中。接着遍历了列表中的所有数字,如果当前值大于 `largest` 中储存的值,将 `largest` 替换为这个值。如果当前值小于目前为止的最大值,`largest` 保持不变。当列表中所有值都被考虑到之后,`largest` 将会是最大值,在这里也就是 100。
如果需要在两个不同的列表中寻找最大值,我们可以重复列表 10-1 中的代码这样程序中就会存在两段相同逻辑的代码,如列表 10-2 所示:
如果需要在两个不同的列表中寻找最大值,我们可以重复列表 10-1 中的代码这样程序中就会存在两段相同逻辑的代码,如列表 10-2 所示:
<span class="filename">Filename: src/main.rs</span>
```rust
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let number_list = vec![34, 50, 25, 100, 65];
let mut largest = numbers[0];
let mut largest = number_list[0];
for number in numbers {
for number in number_list {
if number > largest {
largest = number;
}
@ -62,11 +61,11 @@ fn main() {
println!("The largest number is {}", largest);
let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let mut largest = numbers[0];
let mut largest = number_list[0];
for number in numbers {
for number in number_list {
if number > largest {
largest = number;
}
@ -76,8 +75,7 @@ fn main() {
}
```
<span class="caption">Listing 10-2: Code to find the largest number in *two*
lists of numbers</span>
<span class="caption">列表 10-2寻找 **两个** 数字列表最大值的代码</span>
虽然代码能够执行,但是重复的代码是冗余且容易出错的,并且意味着当更新逻辑时需要修改多处地方的代码。
@ -89,7 +87,7 @@ lists of numbers</span>
在列表 10-3 的程序中将寻找最大值的代码提取到了一个叫做 `largest` 的函数中。这个程序可以找出两个不同数字列表的最大值,不过列表 10-1 中的代码只存在于一个位置:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
fn largest(list: &[i32]) -> i32 {
@ -105,24 +103,23 @@ fn largest(list: &[i32]) -> i32 {
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);
let result = largest(&number_list);
println!("The largest number is {}", result);
# assert_eq!(result, 100);
let numbers = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];
let result = largest(&numbers);
let result = largest(&number_list);
println!("The largest number is {}", result);
# assert_eq!(result, 6000);
}
```
<span class="caption">Listing 10-3: Abstracted code to find the largest number
in two lists</span>
<span class="caption">列表 10-3抽象后的寻找两个数字列表最大值的代码</span>
这个函数有一个参数`list`,它代表会传递给函数的任何具体`i32`值的 slice。函数定义中的`list`代表任何`&[i32]`。当调用`largest`函数时,其代码实际上运行于我们传递的特定值上。
这个函数有一个参数 `list`,它代表会传递给函数的任何具体`i32`值的 slice。函数定义中的 `list` 代表任何 `&[i32]`。当调用 `largest` 函数时,其代码实际上运行于我们传递的特定值上。
从列表 10-2 到列表 10-3 中涉及的机制经历了如下几步:
@ -130,6 +127,6 @@ in two lists</span>
2. 我们将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。
3. 我们将两个具体的存在重复代码的位置替换为了函数调用。
在不同的场景使用不同的方式泛型也可以利用相同的步骤来减少重复代码。与函数体中现在作用于一个抽象的`list`而不是具体值一样,使用泛型的代码也作用于抽象类型。支持泛型背后的概念与你已经了解的支持函数的概念是一样的,不过是实现方式不同。
在不同的场景使用不同的方式,我们也可以利用相同的步骤和泛型来减少重复代码。与函数体中现在作用于一个抽象的 `list` 而不是具体值一样,使用泛型的代码也作用于抽象类型。支持泛型背后的概念与你已经了解的支持函数的概念是一样的,不过是实现方式不同。
如果我们有两个函数,一个寻找一个 `i32` 值的 slice 中的最大项而另一个寻找 `char` 值的 slice 中的最大项该怎么办?该如何消除重复呢?让我们拭目以待!

View File

@ -2,7 +2,7 @@
> [ch10-01-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-01-syntax.md)
> <br>
> commit 55d9e75ffec92e922273c997026bb10613a76578
> commit 56352c28cf3fe0402fa5a7cba73890e314d720eb
泛型用于通常我们放置类型的位置,比如函数签名或结构体,允许我们创建可以代替许多具体数据类型的结构体定义。让我们看看如何使用泛型定义函数、结构体、枚举和方法,并且在本部分的结尾我们会讨论泛型代码的性能。
@ -12,7 +12,8 @@
回到 `largest` 函数上,列表 10-4 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。第一个是从列表 10-3 中提取的寻找 slice 中 `i32` 最大值的函数。第二个函数寻找 slice 中 `char` 的最大值:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
fn largest_i32(list: &[i32]) -> i32 {
@ -40,24 +41,23 @@ fn largest_char(list: &[char]) -> char {
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let number_list = vec![34, 50, 25, 100, 65];
let result = largest_i32(&numbers);
let result = largest_i32(&number_list);
println!("The largest number is {}", result);
# assert_eq!(result, 100);
let chars = vec!['y', 'm', 'a', 'q'];
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest_char(&chars);
let result = largest_char(&char_list);
println!("The largest char is {}", result);
# assert_eq!(result, 'y');
}
```
<span class="caption">Listing 10-4: Two functions that differ only in their
names and the types in their signatures</span>
<span class="caption">列表 10-4两个只在名称和签名中类型有所不同的函数</span>
这里`largest_i32`和`largest_char`有着完全相同的函数体,所以能够将这两个函数变成一个来减少重复就太好了。所幸通过引入一个泛型参数就能实现!
这里 `largest_i32` `largest_char` 有着完全相同的函数体,所以如果能够将这两个函数变成一个来减少重复就太好了。所幸通过引入一个泛型参数就能实现!
为了参数化要定义的函数的签名中的类型,我们需要像给函数的值参数起名那样为这类型参数起一个名字。这里选择了名称 `T`。任何标识符都可以作为类型参数名,选择 `T` 是因为 Rust 的类型命名规范是骆驼命名法CamelCase。另外泛型类型参数的规范也倾向于简短经常仅仅是一个字母。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
@ -73,7 +73,7 @@ fn largest<T>(list: &[T]) -> T {
列表 10-5 展示一个在签名中使用了泛型的统一的 `largest` 函数定义,并向我们展示了如何对 `i32` 值的 slice 或 `char` 值的 slice 调用 `largest` 函数。注意这些代码还不能编译!
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
fn largest<T>(list: &[T]) -> T {
@ -89,25 +89,23 @@ fn largest<T>(list: &[T]) -> T {
}
fn main() {
let numbers = vec![34, 50, 25, 100, 65];
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&numbers);
let result = largest(&number_list);
println!("The largest number is {}", result);
let chars = vec!['y', 'm', 'a', 'q'];
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&chars);
let result = largest(&char_list);
println!("The largest char is {}", result);
}
```
<span class="caption">Listing 10-5: A definition of the `largest` function that
uses generic type parameters but doesn't compile yet</span>
<span class="caption">列表 10-5一个还不能编译的使用泛型参数的 `largest` 函数定义</span>
如果现在就尝试编译这些代码,会出现如下错误:
```
```text
error[E0369]: binary operation `>` cannot be applied to type `T`
|
5 | if item > largest {
@ -130,7 +128,7 @@ what you think. /Carol -->
同样也可以使用 `<>` 语法来定义拥有一个或多个泛型参数类型字段的结构体。列表 10-6 展示了如何定义和使用一个可以存放任何类型的 `x``y` 坐标值的结构体 `Point`
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
struct Point<T> {
@ -144,14 +142,13 @@ fn main() {
}
```
<span class="caption">Listing 10-6: A `Point` struct that holds `x` and `y`
values of type `T`</span>
<span class="caption">列表 10-6`Point` 结构体存放了两个 `T` 类型的值 `x``y`</span>
其语法类似于函数定义中的泛型应用。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。
其语法类似于函数定义中使用泛型。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。
注意 `Point` 的定义中只使用了一个泛型类型,我们想要表达的是结构体 `Point` 对于一些类型 `T` 是泛型的,而且字段 `x``y` **都是** 相同类型的,无论它具体是何类型。如果尝试创建一个有不同类型值的 `Point` 的实例,像列表 10-7 中的代码就不能编译:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
struct Point<T> {
@ -164,12 +161,11 @@ fn main() {
}
```
<span class="caption">Listing 10-7: The fields `x` and `y` must be the same
type because both have the same generic data type `T`</span>
<span class="caption">列表 10-7字段 `x``y` 必须是相同类型,因为他们都有相同的泛型类型 `T`</span>
尝试编译会得到如下错误:
```
```text
error[E0308]: mismatched types
-->
|
@ -183,9 +179,9 @@ error[E0308]: mismatched types
当我们将 5 赋值给 `x`,编译器就知道这个 `Point` 实例的泛型类型 `T` 是一个整型。接着我们将 `y` 指定为 4.0,而它被定义为与 `x` 有着相同的类型,所以出现了类型不匹配的错误。
如果想要一个`x`和`y`可以有不同类型且仍然是泛型的`Point`结构体,我们可以使用多个泛型类型参数。在列表 10-8 中,我们修改`Point`的定义为拥有两个泛型类型`T`和`U`。其中字段`x`是`T`类型的,而字段`y`是`U`类型的:
如果想要定义一个 `x` `y` 可以有不同类型且仍然是泛型的 `Point` 结构体,我们可以使用多个泛型类型参数。在列表 10-8 中,我们修改 `Point` 的定义为拥有两个泛型类型 `T` `U`。其中字段 `x` `T` 类型的,而字段 `y` `U` 类型的:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
struct Point<T, U> {
@ -200,8 +196,7 @@ fn main() {
}
```
<span class="caption">Listing 10-8: A `Point` generic over two types so that
`x` and `y` may be values of different types</span>
<span class="caption">列表 10-8使用两个泛型的 `Point`,这样 `x``y` 可能是不同类型</span>
现在所有这些 `Point` 实例都是被允许的了!你可以在定义中使用任意多的泛型类型参数,不过太多的话代码将难以阅读和理解。如果你处于一个需要很多泛型类型的位置,这可能是一个需要重新组织代码并分隔成一些更小部分的信号。
@ -227,7 +222,7 @@ enum Result<T, E> {
}
```
`Result`枚举有两个泛型类型,`T`和`E`。`Result`有两个成员:`Ok`,它存放一个类型`T`的值,而`Err`则存放一个类型`E`的值。这个定义使得`Result`枚举能很方便的表达任何可能成功(返回`T`类型的值)也可能失败(返回`E`类型的值)的操作。回忆一下列表 9-2 中打开一个文件的场景,当文件被成功打开`T`被放入了`std::fs::File`类型而当打开文件出现问题时`E`被放入了`std::io::Error`类型。
`Result` 枚举有两个泛型类型,`T` `E`。`Result` 有两个成员:`Ok`,它存放一个类型 `T` 的值,而 `Err` 则存放一个类型 `E` 的值。这个定义使得 `Result` 枚举能很方便的表达任何可能成功(返回 `T` 类型的值)也可能失败(返回 `E` 类型的值)的操作。回忆一下列表 9-2 中打开一个文件的场景:当文件被成功打开 `T` 被放入了 `std::fs::File` 类型而当打开文件出现问题时 `E` 被放入了 `std::io::Error` 类型。
当发现代码中有多个只有存放的值的类型有所不同的结构体或枚举定义时,你就应该像之前的函数定义中那样引入泛型类型来减少重复代码。
@ -235,7 +230,7 @@ enum Result<T, E> {
可以像第五章介绍的那样来为其定义中带有泛型的结构体或枚举实现方法。列表 10-9 中展示了列表 10-6 中定义的结构体 `Point<T>`。接着我们在 `Point<T>` 上定义了一个叫做 `x` 的方法来返回字段 `x` 中数据的引用:
<span class="filename">Filename: src/main.rs</span>
<span class="filename">文件名: src/main.rs</span>
```rust
struct Point<T> {
@ -256,9 +251,7 @@ fn main() {
}
```
<span class="caption">Listing 10-9: Implementing a method named `x` on the
`Point<T>` struct that will return a reference to the `x` field, which is of
type `T`.</span>
<span class="caption">列表 10-9`Point<T>` 结构体上实现方法 `x`,它返回 `T` 类型的字段 `x` 的引用</span>
注意必须在 `impl` 后面声明 `T`,这样就可以在 `Point<T>` 上实现的方法中使用它了。

View File

@ -404,7 +404,7 @@ fn first_word<'a>(s: &'a str) -> &'a str {
现在介绍编译器用于判断引用何时不需要明确生命周期注解的规则。第一条规则适用于输入生命周期,而后两条规则则适用于输出生命周期。如果编译器检查完这三条规则并仍然存在没有计算出生命周期的引用,编译器将会停止并生成错误。
1. 每一个是引用的参数都有它自己的生命周期参数。句话说就是,有一个引用参数的函数有一个生命周期参数:`fn foo<'a>(x: &'a i32)`,有两个引用参数的函数有两个不同的生命周期参数,`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`,依此类推。
1. 每一个是引用的参数都有它自己的生命周期参数。句话说就是,有一个引用参数的函数有一个生命周期参数:`fn foo<'a>(x: &'a i32)`,有两个引用参数的函数有两个不同的生命周期参数,`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`,依此类推。
2. 如果只有一个输入生命周期参数,那么它被赋给所有输出生命周期参数:`fn foo<'a>(x: &'a i32) -> &'a i32`。
@ -529,7 +529,7 @@ fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a st
}
```
这个是列表 10-21 中那个返回两个字符串 slice 中最长者的`longest`函数,不过带有一个额外的参数`ann`。`ann`的类型是泛型`T`,它可以被放入任何实现了`where`从句中指定的`Display` trait 的类型。这个额外的参数会在函数比较字符串 slice 的长度之前被打印出来,这也就是为什么`Display` trait bound 是必须的。因为生命周期也是泛型,生命周期参数`'a`和泛型类型参数`T`都位于函数名后的同一尖括号列表中。
这个是列表 10-21 中那个返回两个字符串 slice 中最长者的`longest`函数,不过带有一个额外的参数`ann`。`ann`的类型是泛型`T`,它可以被放入任何实现了`where`从句中指定的`Display` trait 的类型。这个额外的参数会在函数比较字符串 slice 的长度之前被打印出来,这也就是为什么`Display` trait bound 是必须的。因为生命周期也是泛型,所以生命周期参数`'a`和泛型类型参数`T`都位于函数名后的同一尖括号列表中。
## 总结