check to ch10-03

pull/150/head
KaiserY 7 years ago
parent eac25102ee
commit 5f3aa59bb8

@ -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::<IpAddr>().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::<IpAddr>().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 {
}
```
<span class="caption">示例 9-8:一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续</span>
<span class="caption">示例 9-9:一个 `Guess` 类型,它只在值位于 1 和 100 之间时才继续</span>
首先,我们定义了一个包含 `u32` 类型字段 `value` 的结构体 `Guess`。这里是储存猜测值的地方。
@ -111,4 +111,4 @@ impl Guess {
Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。`panic!` 宏代表一个程序无法处理的状态并停止执行而不是使用无效或不正确的值继续处理。Rust 类型系统的 `Result` 枚举代表操作可能会在一种可以恢复的情况下失败。可以使用 `Result` 来告诉代码调用者他需要处理潜在的成功或失败。在适当的场景使用 `panic!``Result` 将会使你的代码在面对无处不在的错误时显得更加可靠。
现在我们已经见识过了标准库中 `Option``Result` 泛型枚举的能力了,让我们聊聊泛型是如何工作的,以及如何在你的代码中利用他们。
现在我们已经见识过了标准库中 `Option``Result` 泛型枚举的能力了,在下一章让我们聊聊泛型是如何工作的,以及如何在你的代码中利用他们。

@ -4,7 +4,7 @@
> <br>
> commit f65676e17d7fc4c0c7cd7275a7bf15447364831a
每一个编程语言都有高效的处理重复概念的工具;在 Rust 中工具之一就是 **泛型***generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。
每一个编程语言都有高效的处理重复概念的工具;在 Rust 中工具之一就是 **泛型***generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。
同理为了编写一份可以用于多种具体值的代码,函数并不知道其参数为何值,这时就可以让函数获取泛型而不是像 `i32``String` 这样的具体值。我们已经使用过第六章的 `Option<T>`,第八章的 `Vec<T>``HashMap<K, V>`,以及第九章的 `Result<T, E>` 这些泛型了。本章会探索如何使用泛型定义我们自己的类型、函数和方法!

@ -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不过让我们先把这个例子放在一边并探索其他那些可以使用泛型类型参数的地方。
<!-- Liz: this is the reason we had the topics in the order we did in the first
draft of this chapter; it's hard to do anything interesting with generic types
@ -309,7 +309,7 @@ fn main() {
### 泛型代码的性能
在阅读本部分的内容的同时你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是Rust 实现泛型的方式意味着你的代码使用泛型类型参数相比指定具体类型并没有任何速度上的损失
在阅读本部分的内容的同时你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是Rust 实现泛型的方式意味着你的代码使用泛型类型参数相比指定具体类型并没有任何速度上的损失
Rust 通过在编译时进行泛型代码的 **单态化***monomorphization*)来保证效率。单态化是一个将泛型代码转变为实际放入的具体类型的特定代码的过程。

@ -2,7 +2,7 @@
> [ch10-02-traits.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-02-traits.md)
> <br>
> commit 1cbcc277af6931d3091fe46a8f379fefae7202db
> commit 131859023a0a6be67168d36dcdc8e2aa43f806fd
trait 允许我们进行另一种抽象:他们让我们可以抽象类型所通用的行为。*trait* 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。在使用泛型类型参数的场景中,可以使用 *trait bounds* 在编译时指定泛型可以是任何实现了某个 trait 的类型,并由此在这个场景下拥有我们希望的功能。
@ -32,7 +32,7 @@ trait 体中可以有多个方法,一行一个方法签名且都以分号结
### 为类型实现 trait
现在我们定义了 `Summarizable` trait接着就可以在多媒体聚合库中需要拥有这个行为的类型上实现它了。示例 10-12 中展示了 `NewsArticle` 结构体上 `Summarizable` trait 的一个实现,它使用标题、作者和创建的位置作为 `summary` 的返回值。对于 `Tweet` 结构体,我们选择将 `summary` 定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 140 字符以内。
现在我们定义了 `Summarizable` trait接着就可以在多媒体聚合库中需要拥有这个行为的类型上实现它了。示例 10-13 中展示了 `NewsArticle` 结构体上 `Summarizable` trait 的一个实现,它使用标题、作者和创建的位置作为 `summary` 的返回值。对于 `Tweet` 结构体,我们选择将 `summary` 定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 140 字符以内。
<span class="filename">文件名: lib.rs</span>
@ -87,7 +87,7 @@ println!("1 new tweet: {}", tweet.summary());
这会打印出 `1 new tweet: horse_ebooks: of course, as you probably already know, people`
注意因为示例 10-12 中我们在相同的 `lib.rs` 里定义了 `Summarizable` trait 和 `NewsArticle``Tweet` 类型,所以他们是位于同一作用域的。如果这个 `lib.rs` 是对应 `aggregator` crate 的,而别人想要利用我们 crate 的功能外加为其 `WeatherForecast` 结构体实现 `Summarizable` trait在实现 `Summarizable` trait 之前他们首先就需要将其导入其作用域中,如示例 10-14 所示:
注意因为示例 10-13 中我们在相同的 `lib.rs` 里定义了 `Summarizable` trait 和 `NewsArticle``Tweet` 类型,所以他们是位于同一作用域的。如果这个 `lib.rs` 是对应 `aggregator` crate 的,而别人想要利用我们 crate 的功能外加为其 `WeatherForecast` 结构体实现 `Summarizable` trait在实现 `Summarizable` trait 之前他们首先就需要将其导入其作用域中,如示例 10-14 所示:
<span class="filename">文件名: lib.rs</span>
@ -159,7 +159,7 @@ println!("New article available! {}", article.summary());
`Summarizable` trait 改变为拥有默认 `summary` 实现并不要求对示例 10-13 中 `Tweet` 和示例 10-14 中 `WeatherForecast``Summarizable` 实现做任何改变:重载一个默认实现的语法与实现没有默认实现的 trait 方法时完全一样的。
默认实现允许调用相同 trait 中的其他方法哪怕这些方法没有默认实现。通过这种方法trait 可以实现很多有用的功能而只需实现一小部分特定内容。我们可以选择让`Summarizable` trait 也拥有一个要求实现 的`author_summary` 方法,接着 `summary` 方法则提供默认实现并调用 `author_summary` 方法:
默认实现允许调用相同 trait 中的其他方法哪怕这些方法没有默认实现。通过这种方法trait 可以实现很多有用的功能而只需实现一小部分特定内容。我们可以选择让`Summarizable` trait 也拥有一个要求实现的`author_summary` 方法,接着 `summary` 方法则提供默认实现并调用 `author_summary` 方法:
```rust
pub trait Summarizable {
@ -198,7 +198,7 @@ println!("1 new tweet: {}", tweet.summary());
注意在重载过的实现中调用默认实现是不可能的。
### trait bounds
### Trait Bounds
现在我们定义了 trait 并在类型上实现了这些 trait也可以对泛型类型参数使用 trait。我们可以限制泛型不再适用于任何类型编译器会确保其被限制为那些实现了特定 trait 的类型,由此泛型就会拥有我们希望其类型所拥有的功能。这被称为指定泛型的 *trait bounds*
@ -216,6 +216,7 @@ trait bounds 连同泛型类型参数声明一同出现,位于尖括号中的
对于拥有多个泛型类型参数的函数,每一个泛型都可以有其自己的 trait bounds。在函数名和参数列表之间的尖括号中指定很多的 trait bound 信息将是难以阅读的,所以有另外一个指定 trait bounds 的语法,它将其移动到函数签名后的 `where` 从句中。所以相比这样写:
```rust,ignore
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
```
@ -278,8 +279,6 @@ error[E0507]: cannot move out of borrowed content
<span class="filename">文件名: src/main.rs</span>
```rust
use std::cmp::PartialOrd;
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
@ -311,7 +310,7 @@ fn main() {
### 使用 trait bound 有条件的实现方法
通过使用带有 trait bound 的泛型 `impl` 块,可以有条件的只为实现了特定 trait 的类型实现方法。例如,示例 10-17 中的类型 `Pair<T>` 总是实现了 `new` 方法,不过只有 `Pair<T>` 内部的 `T` 实现了 `PartialOrd` trait 来允许比较和 `Display` trait 来启用打印,才会实现 `cmp_display`
通过使用带有 trait bound 的泛型 `impl` 块,可以有条件的只为实现了特定 trait 的类型实现方法。例如,示例 10-17 中的类型 `Pair<T>` 总是实现了 `new` 方法,不过只有 `Pair<T>` 内部的 `T` 类型实现了 `PartialOrd` trait 来允许比较和 `Display` trait 来启用打印,才会实现 `cmp_display`
```rust
use std::fmt::Display;
@ -347,7 +346,7 @@ impl<T: Display + PartialOrd> Pair<T> {
```rust,ignore
impl<T: Display> ToString for T {
// ...snip...
// --snip--
}
```

@ -2,7 +2,7 @@
> [ch10-03-lifetime-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-03-lifetime-syntax.md)
> <br>
> commit aa4be9389d18c31f587ebf75cbbb6af39ff4247d
> commit fa0e4403f8350287b034c5b64af752f647ebb5a2
当在第四章讨论引用时我们遗漏了一个重要的细节Rust 中的每一个引用都有其 **生命周期***lifetime*),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明他们的关系,这样就能确保运行时实际使用的引用绝对是有效的。

Loading…
Cancel
Save