Merge pull request #231 from spartucus/master

Several refactors
pull/232/head
KaiserY 6 years ago committed by GitHub
commit 6e515b8cc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,10 +2,10 @@
> [ch09-00-error-handling.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-00-error-handling.md)
> <br>
> commit a764530433720fe09ae2d97874c25341f8322573
> commit 7f0c806e746a133ab344cb2035d31f2a63fb6d79
Rust 对可靠性的执着也延伸到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多特性来处理出现错误的情况。在很多情况下Rust 要求你承认出错的可能性并在编译代码之前就采取行动。通过确保不会只有在将代码部署到生产环境之后才会发现错误来使得程序更可靠。
Rust 将错误组合成两个主要类别:**可恢复错误***recoverable*)和 **不可恢复错误***unrecoverable*)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。
大部分语言并不区分这两类错误并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有 `Result<T, E>` 值,以及 `panic!`,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍 `panic!` 调用,接着会讲到如何返回 `Result<T, E>`最后,我们会讨论当决定是尝试从错误中恢复还是停止执行时需要顾及的权衡考虑
大部分语言并不区分这两类错误并采用类似异常这样方式统一处理他们。Rust 并没有异常。相反,对于可恢复错误有 `Result<T, E>` 值,以及 `panic!`,它在遇到不可恢复错误时停止程序执行。这一章会首先介绍 `panic!` 调用,接着会讲到如何返回 `Result<T, E>`此外,我们将在决定是尝试从错误中恢复还是停止执行时探讨注意事项

@ -2,13 +2,13 @@
> [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 a764530433720fe09ae2d97874c25341f8322573
> commit 609909ec443042399858d1f679b0df1d6d0eba22
突然有一天糟糕的事情发生了而你对此束手无策。对于这种情况Rust 有 `panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
> ### Panic 中的栈展开与终止
>
> 当出现 `panic!` 时,程序默认会开始 **展开***unwinding*),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 **终止***abort*),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,可以由 panic 时展开切换为终止,通过在 *Cargo.toml*`[profile]` 部分增加 `panic = 'abort'`。例如,如果你想要在发布模式中 panic 时直接终止:
> 当出现 `panic!` 时,程序默认会开始 **展开***unwinding*),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 **终止***abort*这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好panic 时通过在 *Cargo.toml*`[profile]` 部分增加 `panic = 'abort'`可以由展开切换为终止。例如如果你想要在release模式中 panic 时直接终止:
>
> ```toml
> [profile.release]
@ -125,4 +125,4 @@ stack backtrace:
示例 9-2 的输出中backtrace 的 11 行指向了我们项目中造成问题的行:*src/main.rs* 的第 4 行。如果你不希望程序 panic第一个提到我们编写的代码行的位置是你应该开始调查的以便查明是什么值如何在这个地方引起了 panic。在上面的例子中我们故意编写会 panic 的代码来演示如何使用 backtrace修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你的代码出现了 panic你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic以及应当如何处理才能避免这个问题。
本章后面会再次回到 `panic!` 并讲到何时应该及何时不应该使用这个方式。接下来,我们来看看如何使用 `Result` 来从错误中恢复。
本章后面的小节“panic! 还是不 panic!”会再次回到 `panic!` 并讲到何时应该及何时不应该使用这个方式。接下来,我们来看看如何使用 `Result` 来从错误中恢复。

@ -2,9 +2,9 @@
> [ch09-02-recoverable-errors-with-result.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-02-recoverable-errors-with-result.md)
> <br>
> commit a764530433720fe09ae2d97874c25341f8322573
> commit 347a8bf6beaf34cef5c4e82c2171f498f081485e
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而操作失败,这时我们可能想要创建这个文件而不是终止进程。
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而失败,此时我们可能想要创建这个文件而不是终止进程。
回忆一下第二章 “使用 `Result` 类型来处理潜在的错误” 部分中的那个 `Result` 枚举,它定义有如下两个成员,`Ok` 和 `Err`
@ -33,7 +33,7 @@ fn main() {
<span class="caption">示例 9-3打开文件</span>
如何知道 `File::open` 返回一个 `Result` 呢?我们可以查看标准库 API 文档,或者可以直接问编译器!如果给 `f` 某个我们知道 **不是** 函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们 `f` 的类型 **应该** 是什么.让我们试试:我们知道 `File::open` 的返回值不是 `u32` 类型的,所以将 `let f` 语句改为如下:
如何知道 `File::open` 返回一个 `Result` 呢?我们可以查看标准库 API 文档,或者可以直接问编译器!如果给 `f` 某个我们知道 **不是** 函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们 `f` 的类型 **应该** 是什么。让我们试试!我们知道 `File::open` 的返回值不是 `u32` 类型的,所以将 `let f` 语句改为如下:
```rust,ignore
let f: u32 = File::open("hello.txt");
@ -302,4 +302,4 @@ error[E0277]: the trait bound `(): std::ops::Try` is not satisfied
错误指出只能在返回 `Result` 的函数中使用问号运算符。在不返回 `Result` 的函数中,当调用其他返回 `Result` 的函数时,需要使用 `match``Result` 的方法之一来处理,而不能用 `?` 将潜在的错误传播给调用者。
现在我们讨论过了调用 `panic!` 或返回 `Result` 的细节,是时候回他们各自适合哪些场景的话题了。
现在我们讨论过了调用 `panic!` 或返回 `Result` 的细节,是时候回他们各自适合哪些场景的话题了。

@ -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 3e79fb6f3f85ac6d4a0ce46612e5a7381dc7f1b1
> commit 609909ec443042399858d1f679b0df1d6d0eba22
那么,该如何决定何时应该 `panic!` 以及何时应该返回 `Result` 呢?如果代码 panic就没有恢复的可能。你可以选择对任何错误场景都调用 `panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回 `Result` 值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为 `Err` 是不可恢复的,所以他们也可能会调用 `panic!` 并将可恢复的错误变成了不可恢复的错误。因此返回 `Result` 是定义可能会失败的函数的一个好的默认选择。
@ -12,13 +12,13 @@
当你编写一个示例来展示一些概念时,在拥有健壮的错误处理代码的同时也会使得例子不那么明确。例如,调用一个类似 `unwrap` 这样可能 `panic!` 的方法可以被理解为一个你实际希望程序处理错误方式的占位符,它根据其余代码运行方式可能会各不相同。
类似的,`unwrap` 和 `expect` 方法在原型设计时非常方便,在你决定该如何处理错误之前。他们在代码中留下了明显的记号,以便你准备使程序变得更健壮时作为参考
类似地,在我们准备好决定如何处理错误之前,`unwrap`和`expect`方法在原型设计时非常方便。当我们准备好让程序更加健壮时,它们会在代码中留下清晰的标记
如果方法调用在测试中失败了,我们希望这个测试都失败,即便这个方法并不是需要测试的功能。因为 `panic!` 是测试如何被标记为失败的,调用 `unwrap``expect` 都是非常有道理的
如果方法调用在测试中失败了,我们希望这个测试都失败,即便这个方法并不是需要测试的功能。因为 `panic!` 是测试如何被标记为失败的,调用 `unwrap``expect` 就是应该发生的事情
### 当你比编译器知道更多的情况
当你有一些其他的逻辑来确保 `Result` 会是 `Ok`的时候调用 `unwrap` 也是合适的,虽然编译器无法理解这种逻辑。仍然会有一个 `Result` 值等着你处理:总的来说你调用的任何操作都有失败的可能性,即便在特定情况下逻辑上是不可能的。如果通过人工检查代码来确保永远也不会出现 `Err` 值,那么调用 `unwrap` 也是完全可以接受的,这里是一个例子:
当你有一些其他的逻辑来确保 `Result` 会是 `Ok`时,调用 `unwrap` 也是合适的,虽然编译器无法理解这种逻辑。你仍然需要处理一个 `Result` 值:即使在你的特定情况下逻辑上是不可能的,你所调用的任何操作仍然有可能失败。如果通过人工检查代码来确保永远也不会出现 `Err` 值,那么调用 `unwrap` 也是完全可以接受的,这里是一个例子:
```rust
use std::net::IpAddr;
@ -38,7 +38,7 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是 `panic!` 并警告使用你的库的人他的代码中有 bug 以便他能在开发时就修复它。类似的,`panic!` 通常适合调用不能够控制的外部代码时,这时无法修复其返回的无效状态。
无论代码编写的多么好,当有害状态是预期会出现时,返回 `Result` 仍要比调用 `panic!` 更为合适。这样的例子包括解析器接收到错误数据,或者 HTTP 请求返回一个表明触发了限流的状态。在这些例子中,应该通过返回 `Result` 来表明失败预期是可能的,这样将有害状态向上传播,这样调用者就可以决定该如何处理这个问题。使用 `panic!` 来处理这些情况就不是最好的选择。
无论代码编写的多么好,当有害状态是预期会出现时,返回 `Result` 仍要比调用 `panic!` 更为合适。这样的例子包括解析器接收到错误数据,或者 HTTP 请求返回一个表明触发了限流的状态。在这些例子中,应该通过返回 `Result` 来表明失败预期是可能的,这样将有害状态向上传播,调用者就可以决定该如何处理这个问题。使用 `panic!` 来处理这些情况就不是最好的选择。
当代码对值进行操作时,应该首先验证值是有效的,并在其无效时 `panic!`。这主要是出于安全的原因:尝试操作无效数据会暴露代码漏洞,这就是标准库在尝试越界访问数组时会 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存是一个常见的安全隐患。函数通常都遵循 **契约***contracts*):他们的行为只有在输入满足特定条件时才能得到保证。当违反契约时 panic 是有道理的,因为这通常代表调用方的 bug而且这也不是那种你希望调用方必须处理的错误。事实上也没有合理的方式来恢复调用方的代码调用方的 **程序员** 需要修复其代码。函数的契约,尤其是当违反它会造成 panic 的契约,应该在函数的 API 文档中得到解释。
@ -46,7 +46,7 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
### 创建自定义类型作为验证
让我们用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型作为验证。回忆一下第二章的猜猜看游戏,它的代码请求用户猜测一个 1 到 100 之间的数字,在将其与秘密数字做比较之前我们事实上从未验证用户的猜测是位于这两个数字之间的,只保证它为正。在当前情况下其影响并不是很严重“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。
让我们使用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型以进行验证。回忆一下第二章的猜猜看游戏,我们的代码要求用户猜测一个 1 到 100 之间的数字,在将其与秘密数字做比较之前我们从未验证用户的猜测是位于这两个数字之间的,我们只验证它是否为正。在这种情况下其影响并不是很严重“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。
一种实现方式是将猜测解析成 `i32` 而不仅仅是 `u32`,来默许输入负数,接着检查数字是否在范围内:
@ -111,4 +111,4 @@ impl Guess {
Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。`panic!` 宏代表一个程序无法处理的状态并停止执行而不是使用无效或不正确的值继续处理。Rust 类型系统的 `Result` 枚举代表操作可能会在一种可以恢复的情况下失败。可以使用 `Result` 来告诉代码调用者他需要处理潜在的成功或失败。在适当的场景使用 `panic!``Result` 将会使你的代码在面对无处不在的错误时显得更加可靠。
现在我们已经见识过了标准库中 `Option``Result` 泛型枚举的能力了,在下一章让我们聊聊泛型是如何工作的,以及如何在你的代码中用他们。
现在我们已经见识过了标准库中 `Option``Result` 泛型枚举的能力了,在下一章让我们聊聊泛型是如何工作的,以及如何在你的代码中使用他们。

Loading…
Cancel
Save