|
|
@ -2,7 +2,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/main/src/ch09-03-to-panic-or-not-to-panic.md)
|
|
|
|
> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/main/src/ch09-03-to-panic-or-not-to-panic.md)
|
|
|
|
> <br>
|
|
|
|
> <br>
|
|
|
|
> commit 199ca99926f232ee7f581a917eada4b65ff21754
|
|
|
|
> commit a87db22c297ff9b029f4458c8f303606df767c0b
|
|
|
|
|
|
|
|
|
|
|
|
那么,该如何决定何时应该 `panic!` 以及何时应该返回 `Result` 呢?如果代码 panic,就没有恢复的可能。你可以选择对任何错误场景都调用 `panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回 `Result` 值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为 `Err` 是不可恢复的,所以他们也可能会调用 `panic!` 并将可恢复的错误变成了不可恢复的错误。因此返回 `Result` 是定义可能会失败的函数的一个好的默认选择。
|
|
|
|
那么,该如何决定何时应该 `panic!` 以及何时应该返回 `Result` 呢?如果代码 panic,就没有恢复的可能。你可以选择对任何错误场景都调用 `panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回 `Result` 值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为 `Err` 是不可恢复的,所以他们也可能会调用 `panic!` 并将可恢复的错误变成了不可恢复的错误。因此返回 `Result` 是定义可能会失败的函数的一个好的默认选择。
|
|
|
|
|
|
|
|
|
|
|
@ -18,13 +18,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
### 当我们比编译器知道更多的情况
|
|
|
|
### 当我们比编译器知道更多的情况
|
|
|
|
|
|
|
|
|
|
|
|
当你有一些其他的逻辑来确保 `Result` 会是 `Ok` 值时,调用 `unwrap` 也是合适的,虽然编译器无法理解这种逻辑。你仍然需要处理一个 `Result` 值:即使在你的特定情况下逻辑上是不可能的,你所调用的任何操作仍然有可能失败。如果通过人工检查代码来确保永远也不会出现 `Err` 值,那么调用 `unwrap` 也是完全可以接受的,这里是一个例子:
|
|
|
|
当你有一些其他的逻辑来确保 `Result` 会是 `Ok` 值时,调用 `unwrap` 或者 `expect` 也是合适的,虽然编译器无法理解这种逻辑。你仍然需要处理一个 `Result` 值:即使在你的特定情况下逻辑上是不可能的,你所调用的任何操作仍然有可能失败。如果通过人工检查代码来确保永远也不会出现 `Err` 值,那么调用 `unwrap` 也是完全可以接受的,这里是一个例子:
|
|
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
```rust
|
|
|
|
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-08-unwrap-that-cant-fail/src/main.rs:here}}
|
|
|
|
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-08-unwrap-that-cant-fail/src/main.rs:here}}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
我们通过解析一个硬编码的字符来创建一个 `IpAddr` 实例。可以看出 `127.0.0.1` 是一个有效的 IP 地址,所以这里使用 `unwrap` 是可以接受的。然而,拥有一个硬编码的有效的字符串也不能改变 `parse` 方法的返回值类型:它仍然是一个 `Result` 值,而编译器仍然会要求我们处理这个 `Result`,好像还是有可能出现 `Err` 成员那样。这是因为编译器还没有智能到可以识别出这个字符串总是一个有效的 IP 地址。如果 IP 地址字符串来源于用户而不是硬编码进程序中的话,那么就 **确实** 有失败的可能性,这时就绝对需要我们以一种更健壮的方式处理 `Result` 了。
|
|
|
|
我们通过解析一个硬编码的字符来创建一个 `IpAddr` 实例。可以看出 `127.0.0.1` 是一个有效的 IP 地址,所以这里使用 `expect` 是可以接受的。然而,拥有一个硬编码的有效的字符串也不能改变 `parse` 方法的返回值类型:它仍然是一个 `Result` 值,而编译器仍然会要求我们处理这个 `Result`,好像还是有可能出现 `Err` 成员那样。这是因为编译器还没有智能到可以识别出这个字符串总是一个有效的 IP 地址。如果 IP 地址字符串来源于用户而不是硬编码进程序中的话,那么就 **确实** 有失败的可能性,这时就绝对需要我们以一种更健壮的方式处理 `Result` 了。提及这个 IP 地址是硬编码的假设会促使我们将来把 `expect` 替换为更好的错误处理,我们应该从其它代码获取 IP 地址。
|
|
|
|
|
|
|
|
|
|
|
|
### 错误处理指导原则
|
|
|
|
### 错误处理指导原则
|
|
|
|
|
|
|
|
|
|
|
@ -34,11 +34,11 @@
|
|
|
|
* 在此之后代码的运行依赖于不处于这种有害状态,而不是在每一步都检查是否有问题。
|
|
|
|
* 在此之后代码的运行依赖于不处于这种有害状态,而不是在每一步都检查是否有问题。
|
|
|
|
* 没有可行的手段来将有害状态信息编码进所使用的类型中的情况。我们会在第十七章 [“将状态和行为编码为类型”][encoding] 部分通过一个例子来说明我们的意思。
|
|
|
|
* 没有可行的手段来将有害状态信息编码进所使用的类型中的情况。我们会在第十七章 [“将状态和行为编码为类型”][encoding] 部分通过一个例子来说明我们的意思。
|
|
|
|
|
|
|
|
|
|
|
|
如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是 `panic!` 并警告使用你的库的人他的代码中有 bug 以便他能在开发时就修复它。类似的,如果你正在调用不受你控制的外部代码,并且它返回了一个你无法修复的无效状态,那么 `panic!` 往往是合适的。
|
|
|
|
如果别人调用你的代码并传递了一个没有意义的值,尽最大可能返回一个错误,如此库的用户就可以决定在这种情况下改如何处理。然而在继续执行代码是不安全或有害的情况下,最好的选择可能是调用 `panic!` 并警告库的用户他们的代码中有 bug,这样他们就会在开发时进行修复。类似的,如果你正在调用不受你控制的外部代码,并且它返回了一个你无法修复的无效状态,那么 `panic!` 往往是合适的。
|
|
|
|
|
|
|
|
|
|
|
|
然而当错误预期会出现时,返回 `Result` 仍要比调用 `panic!` 更为合适。这样的例子包括解析器接收到格式错误的数据,或者 HTTP 请求返回了一个表明触发了限流的状态。在这些例子中,应该通过返回 `Result` 来表明失败预期是可能的,这样将有害状态向上传播,调用者就可以决定该如何处理这个问题。使用 `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` 这样的无符号整型,也会确保它永远不为负。
|
|
|
|
|
|
|
|
|
|
|
|