|
|
|
@ -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` 泛型枚举的能力了,在下一章让我们聊聊泛型是如何工作的,以及如何在你的代码中使用他们。
|
|
|
|
|