diff --git a/src/ch09-03-to-panic-or-not-to-panic.md b/src/ch09-03-to-panic-or-not-to-panic.md index 93abbbc..b37970a 100644 --- a/src/ch09-03-to-panic-or-not-to-panic.md +++ b/src/ch09-03-to-panic-or-not-to-panic.md @@ -8,7 +8,7 @@ 有一些情况 panic 比返回 `Result` 更为合适,不过他们并不常见。让我们讨论一下为何在示例、代码原型和测试中,以及那些人们认为不会失败而编译器不这么看的情况下, panic 是合适的,最后会总结一些在库代码中如何决定是否要 panic 的通用指导原则。 -### 示例、代码原型和测试:非常适合 panic +### 示例、代码原型和测试都非常适合 panic 当你编写一个示例来展示一些概念时,在拥有健壮的错误处理代码的同时也会使得例子不那么明确。例如,调用一个类似 `unwrap` 这样可能 `panic!` 的方法可以被理解为一个你实际希望程序处理错误方式的占位符,它根据其余代码运行方式可能会各不相同。 @@ -23,24 +23,24 @@ ```rust use std::net::IpAddr; -let home = "127.0.0.1".parse::().unwrap(); +let home: IpAddr = "127.0.0.1".parse().unwrap(); ``` -我们通过解析一个硬编码的字符来创建一个 `IpAddr`实例。可以看出 `127.0.0.1` 是一个有效的 IP 地址,所以这里使用 `unwrap` 是没有问题的。然而,拥有一个硬编码的有效的字符串也不能改变 `parse` 方法的返回值类型:它仍然是一个 `Result` 值,而编译器仍然就好像还是有可能出现 `Err` 成员那样要求我们处理 `Result`,因为编译器还没有智能到可以识别出这个字符串总是一个有效的 IP 地址。如果 IP 地址字符串来源于用户而不是硬编码进程序中的话,那么就 **确实** 有失败的可能性,这时就绝对需要我们以一种更健壮的方式处理 `Result` 了。 +我们通过解析一个硬编码的字符来创建一个 `IpAddr` 实例。可以看出 `127.0.0.1` 是一个有效的 IP 地址,所以这里使用 `unwrap` 是可以接受的。然而,拥有一个硬编码的有效的字符串也不能改变 `parse` 方法的返回值类型:它仍然是一个 `Result` 值,而编译器仍然就好像还是有可能出现 `Err` 成员那样要求我们处理 `Result`,因为编译器还没有智能到可以识别出这个字符串总是一个有效的 IP 地址。如果 IP 地址字符串来源于用户而不是硬编码进程序中的话,那么就 **确实** 有失败的可能性,这时就绝对需要我们以一种更健壮的方式处理 `Result` 了。 ### 错误处理指导原则 -在当有可能会导致有害状态的情况下建议使用 `panic!`——在这里,有害状态是指当一些假设、保证、协议或不可变性被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值——外加如下几种情况: +在当有可能会导致有害状态的情况下建议使用 `panic!` —— 在这里,有害状态是指当一些假设、保证、协议或不可变性被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值 —— 外加如下几种情况: * 有害状态并不包含 **预期** 会偶尔发生的错误 -* 之后的代码的运行依赖于不再处于这种有害状态 +* 之后的代码的运行依赖于处于这种有害状态 * 当没有可行的手段来将有害状态信息编码进所使用的类型中的情况 如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是 `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` 这样的无符号整型,也会确保它永远不为负。 @@ -52,7 +52,7 @@ let home = "127.0.0.1".parse::().unwrap(); ```rust,ignore loop { - // snip + // --snip-- let guess: i32 = match guess.trim().parse() { Ok(num) => num, @@ -65,7 +65,7 @@ loop { } match guess.cmp(&secret_number) { - // snip + // --snip-- } ``` @@ -73,7 +73,7 @@ loop { 然而,这并不是一个理想的解决方案:程序只处理 1 到 100 之间的值是绝对不可取的,而且如果有很多函数都有这样的要求,在每个函数中都有这样的检查将是非常冗余的(并可能潜在的影响性能)。 -相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全的在函数签名中使用新类型并相信他们接收到的值。示例 9-8 中展示了一个定义 `Guess` 类型的方法,只有在 `new` 函数接收到 1 到 100 之间的值时才会创建 `Guess` 的实例: +相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全的在函数签名中使用新类型并相信他们接收到的值。示例 9-9 中展示了一个定义 `Guess` 类型的方法,只有在 `new` 函数接收到 1 到 100 之间的值时才会创建 `Guess` 的实例: ```rust pub struct Guess { @@ -97,7 +97,7 @@ impl Guess { } ``` -示例 9-8:一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续 +示例 9-9:一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续 首先,我们定义了一个包含 `u32` 类型字段 `value` 的结构体 `Guess`。这里是储存猜测值的地方。 @@ -111,4 +111,4 @@ impl Guess { Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。`panic!` 宏代表一个程序无法处理的状态,并停止执行而不是使用无效或不正确的值继续处理。Rust 类型系统的 `Result` 枚举代表操作可能会在一种可以恢复的情况下失败。可以使用 `Result` 来告诉代码调用者他需要处理潜在的成功或失败。在适当的场景使用 `panic!` 和 `Result` 将会使你的代码在面对无处不在的错误时显得更加可靠。 -现在我们已经见识过了标准库中 `Option` 和 `Result` 泛型枚举的能力了,让我们聊聊泛型是如何工作的,以及如何在你的代码中利用他们。 \ No newline at end of file +现在我们已经见识过了标准库中 `Option` 和 `Result` 泛型枚举的能力了,在下一章让我们聊聊泛型是如何工作的,以及如何在你的代码中利用他们。 \ No newline at end of file diff --git a/src/ch10-00-generics.md b/src/ch10-00-generics.md index 04410fa..3039c8e 100644 --- a/src/ch10-00-generics.md +++ b/src/ch10-00-generics.md @@ -4,7 +4,7 @@ >
> commit f65676e17d7fc4c0c7cd7275a7bf15447364831a -每一个编程语言都有高效的处理重复概念的工具;在 Rust 中工具之一就是 **泛型**(*generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。 +每一个编程语言都有高效的处理重复概念的工具;在 Rust 中其工具之一就是 **泛型**(*generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。 同理为了编写一份可以用于多种具体值的代码,函数并不知道其参数为何值,这时就可以让函数获取泛型而不是像 `i32` 或 `String` 这样的具体值。我们已经使用过第六章的 `Option`,第八章的 `Vec` 和 `HashMap`,以及第九章的 `Result` 这些泛型了。本章会探索如何使用泛型定义我们自己的类型、函数和方法! diff --git a/src/ch10-01-syntax.md b/src/ch10-01-syntax.md index 650b6a5..19a5562 100644 --- a/src/ch10-01-syntax.md +++ b/src/ch10-01-syntax.md @@ -114,7 +114,7 @@ error[E0369]: binary operation `>` cannot be applied to type `T` note: an implementation of `std::cmp::PartialOrd` might be missing for `T` ``` -注释中提到了 `std::cmp::PartialOrd`,这是一个 *trait*。下一部分会讲到 trait,不过简单来说,这个错误表明 `largest` 的函数体不能适用于 `T` 的所有可能的类型;因为在函数体需要比较 `T` 类型的值,不过它只能用于我们知道如何排序的类型。标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的排序功能。在下一部分会再次回到 trait 并讲解如何为泛型指定一个 trait,不过让我们先把这个例子放在一边并探索其他那些可以使用泛型类型参数的地方。 +注释中提到了 `std::cmp::PartialOrd`,这是一个 *trait*。下一部分会讲到 trait,不过简单来说,这个错误表明 `largest` 的函数体不能适用于 `T` 的所有可能的类型;因为在函数体需要比较 `T` 类型的值,不过它只能用于我们知道如何排序的类型。标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能。在下一部分会再次回到 trait 并讲解如何为泛型指定一个 trait,不过让我们先把这个例子放在一边并探索其他那些可以使用泛型类型参数的地方。