update ch10-01 & ch10-02

pull/680/head
KaiserY 2 years ago
parent 207fd3e207
commit adb527a675

@ -2,7 +2,7 @@
> [ch10-00-generics.md](https://github.com/rust-lang/book/blob/main/src/ch10-00-generics.md)
> <br>
> commit 9c0fa2714859738ff73cbbb829592e4c037d7e46
> commit 4aa96a3d20570f868bd20e8e3e865b047284be30
每一个编程语言都有高效处理重复概念的工具。在 Rust 中其工具之一就是 **泛型***generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。

@ -2,7 +2,7 @@
> [ch10-01-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch10-01-syntax.md)
> <br>
> commit 6a23b2c7ffe392d1329855444a1b3a88e93982b6
> commit e7c688559e6881257760dfe58567e19e59b14179
我们可以使用泛型为像函数签名或结构体这样的项创建定义,这样它们就可以用于多种不同的具体数据类型。让我们看看如何使用泛型定义函数、结构体、枚举和方法,然后我们将讨论泛型如何影响代码性能。
@ -10,7 +10,7 @@
当使用泛型定义函数时,本来在函数签名中指定参数和返回值的类型的地方,会改用泛型来表示。采用这种技术,使得代码适应性更强,从而为函数的调用者提供更多的功能,同时也避免了代码的重复。
回到 `largest` 函数,示例 10-4 中展示了两个函数,它们的功能都是寻找 slice 中最大值。
回到 `largest` 函数,示例 10-4 中展示了两个函数,它们的功能都是寻找 slice 中最大值。接着我们使用泛型将其合并为一个函数。
<span class="filename">文件名src/main.rs</span>
@ -22,7 +22,7 @@
`largest_i32` 函数是从示例 10-3 中摘出来的,它用来寻找 slice 中最大的 `i32`。`largest_char` 函数寻找 slice 中最大的 `char`。因为两者函数体的代码是一样的,我们可以定义一个函数,再引进泛型参数来消除这种重复。
为了参数化新函数中的这些类型,我们也需要为类型参数取个名字,道理和给函数的形参起名一样。任何标识符都可以作为类型参数的名字。这里选用 `T`因为传统上来说Rust 的参数名字都比较短通常就只有一个字母同时Rust 类型名的命名规范是骆驼命名法CamelCase。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
为了参数化这个新函数中的这些类型,我们也需要为类型参数取个名字,道理和给函数的形参起名一样。任何标识符都可以作为类型参数的名字。这里选用 `T`因为传统上来说Rust 的参数名字都比较短通常就只有一个字母同时Rust 类型名的命名规范是骆驼命名法CamelCase。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
如果要在函数体中使用参数,就必须在函数签名中声明它的名字,好让编译器知道这个名字指代的是什么。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 `largest` 函数,类型参数声明位于函数名称与参数列表中间的尖括号 `<>` 中,像这样:
@ -30,7 +30,7 @@
fn largest<T>(list: &[T]) -> T {
```
可以这样理解这个定义:函数 `largest` 有泛型类型 `T`。它有个参数 `list`,其类型是元素为 `T` 的 slice。`largest` 函数的返回值类型也是 `T`
可以这样理解这个定义:函数 `largest` 有泛型类型 `T`。它有个参数 `list`,其类型是元素为 `T` 的 slice。`largest` 函数会返回一个与 `T` 相同类型的引用
示例 10-5 中的 `largest` 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 `largest` 函数,把 `i32` 值的 slice 或 `char` 值的 slice 传给它。请注意这些代码还不能编译,不过稍后在本章会解决这个问题。
@ -48,13 +48,11 @@ fn largest<T>(list: &[T]) -> T {
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt}}
```
注释中提到了 `std::cmp::PartialOrd`,这是一个 *trait*。下一部分会讲到 trait。不过简单来说这个错误表明 `largest` 的函数体不能适用于 `T` 的所有可能的类型。因为在函数体需要比较 `T` 类型的值,不过它只能用于我们知道如何排序的类型。为了开启比较功能,标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能(查看附录 C 获取该 trait 的更多信息)。
标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能。在 [“trait 作为参数”][traits-as-parameters] 部分会讲解如何指定泛型实现特定的 trait不过让我们先探索其他使用泛型参数的方法。
帮助说明中提到了 `std::cmp::PartialOrd`,这是一个 *trait*。下一部分会讲到 trait。不过简单来说这个错误表明 `largest` 的函数体不能适用于 `T` 的所有可能的类型。因为在函数体需要比较 `T` 类型的值,不过它只能用于我们知道如何排序的类型。为了开启比较功能,标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能(查看附录 C 获取该 trait 的更多信息)。标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能。依照帮助说明中的建议,我们限制 `T` 只对实现了 `PartialOrd` 的类型有效后代码就可以编译了,因为标准库为 `i32``char` 实现了 `PartialOrd`
### 结构体定义中的泛型
同样也可以用 `<>` 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例 10-6 展示了如何定义和使用一个可以存放任何类型的 `x``y` 坐标值的结构体 `Point`
同样也可以用 `<>` 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例 10-6 定义了一个可以存放任何类型的 `x``y` 坐标值的结构体 `Point`
<span class="filename">文件名src/main.rs</span>
@ -92,7 +90,7 @@ fn largest<T>(list: &[T]) -> T {
<span class="caption">示例 10-8使用两个泛型的 `Point`,这样 `x``y` 可能是不同类型</span>
现在所有这些 `Point` 实例都合法了!你可以在定义中使用任意多的泛型类型参数,不过太多的话,代码将难以阅读和理解。当你的代码中需要许多泛型类型时,它可能表明你的代码需要重构,分解成更小的结构。
现在所有这些 `Point` 实例都合法了!你可以在定义中使用任意多的泛型类型参数,不过太多的话,代码将难以阅读和理解。当你发现代码中需要很多泛型时,这可能表明你的代码需要重构分解成更小的结构。
### 枚举定义中的泛型
@ -134,9 +132,11 @@ enum Result<T, E> {
这里在 `Point<T>` 上定义了一个叫做 `x` 的方法来返回字段 `x` 中数据的引用:
注意必须在 `impl` 后面声明 `T`,这样就可以在 `Point<T>` 上实现的方法中使用它了。在 `impl` 之后声明泛型 `T` ,这样 Rust 就知道 `Point` 的尖括号中的类型是泛型而不是具体类型。因为再次声明了泛型,我们可以为泛型参数选择一个与结构体定义中声明的泛型参数所不同的名称,不过依照惯例使用了相同的名称。`impl` 中编写的方法声明了泛型类型可以定位为任何类型的实例,不管最终替换泛型类型的是何具体类型。
注意必须在 `impl` 后面声明 `T`,这样就可以在 `Point<T>` 上实现的方法中使用 `T` 了。通过在 `impl` 之后声明泛型 `T`Rust 就知道 `Point` 的尖括号中的类型是泛型而不是具体类型。我们可以为泛型参数选择一个与结构体定义中声明的泛型参数所不同的名称,不过依照惯例使用了相同的名称。`impl` 中编写的方法声明了泛型类型可以定位为任何类型的实例,不管最终替换泛型类型的是何具体类型。
定义方法时也可以为泛型指定限制constraint。例如可以选择为 `Point<f32>` 实例实现方法,而不是为泛型 `Point` 实例。示例 10-10 展示了一个没有在 `impl` 之后(的尖括号)声明泛型的例子,这里使用了一个具体类型,`f32`
另一个选择是定义方法适用于某些有限制constraint的泛型类型。例如可以选择为 `Point<f32>` 实例实现方法,而不是为泛型 `Point` 实例。示例 10-10 展示了一个没有在 `impl` 之后(的尖括号)声明泛型的例子,这里使用了一个具体类型,`f32`
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-10/src/main.rs:here}}
@ -162,22 +162,22 @@ enum Result<T, E> {
### 泛型代码的性能
在阅读本部分内容的同时,你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是Rust 实现了泛型,使得使用泛型类型参数的代码相比使用具体类型并没有任何速度上的损失
在阅读本部分内容的同时,你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是泛型并不会使程序比具体类型运行得慢
Rust 通过在编译时进行泛型代码的 **单态化***monomorphization*)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。
编译器所做的工作正好与示例 10-5 中我们创建泛型函数的步骤相反。编译器寻找所有泛型代码被调用的位置并使用泛型代码针对具体类型生成代码。
这这个过程中,编译器所做的工作正好与示例 10-5 中我们创建泛型函数的步骤相反。编译器寻找所有泛型代码被调用的位置并使用泛型代码针对具体类型生成代码。
让我们看看一个使用标准库中 `Option` 枚举的例子
让我们看看这如何用于标准库中的 `Option` 枚举
```rust
let integer = Some(5);
let float = Some(5.0);
```
当 Rust 编译这些代码的时候,它会进行单态化。编译器会读取传递给 `Option<T>` 的值并发现有两种 `Option<T>`:一个对应 `i32` 另一个对应 `f64`。为此,它会将泛型定义 `Option<T>` 展开为 `Option_i32` 和 `Option_f64`,接着将泛型定义替换为这两个具体的定义。
当 Rust 编译这些代码的时候,它会进行单态化。编译器会读取传递给 `Option<T>` 的值并发现有两种 `Option<T>`:一个对应 `i32` 另一个对应 `f64`。为此,它会将泛型定义 `Option<T>` 展开为两个针对 `i32` 和 `f64` 的定义,接着将泛型定义替换为这两个具体的定义。
编译器生成的单态化版本的代码看起来像这样,并包含将泛型 `Option<T>` 替换为编译器创建的具体定义后的用例代码
编译器生成的单态化版本的代码看起来像这样(编译器会使用不同于如下假想的名字)
<span class="filename">文件名src/main.rs</span>
@ -198,6 +198,4 @@ fn main() {
}
```
我们可以使用泛型来编写不重复的代码,而 Rust 将会为每一个实例编译其特定类型的代码。这意味着在使用泛型时没有运行时开销;当代码运行,它的执行效率就跟好像手写每个具体定义的重复代码一样。这个单态化过程正是 Rust 泛型在运行时极其高效的原因。
[traits-as-parameters]: ch10-02-traits.html#trait-作为参数
泛型 `Option<T>` 被编译器替换为了具体的定义。因为 Rust 会将每种情况下的泛型代码编译为具体类型,使用泛型没有运行时开销。当代码运行时,它的执行效率就跟好像手写每个具体定义的重复代码一样。这个单态化过程正是 Rust 泛型在运行时极其高效的原因。

@ -4,7 +4,7 @@
> <br>
> commit 3c2ca8528c3b92b7d30e73f2e8a1b84b2f68b0c8
*trait* 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 *trait bounds* 指定泛型是任何拥有特定行为的类型。
*trait* 定义了某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 *trait bounds* 指定泛型是任何拥有特定行为的类型。
> 注意:*trait* 类似于其他语言中的常被称为 **接口***interfaces*)的功能,虽然有一些不同。
@ -60,7 +60,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
有时为 trait 中的某些或全部方法提供默认的行为,而不是在每个类型的每个实现中都定义自己的行为是很有用的。这样当为某个特定类型实现 trait 时,可以选择保留或重载每个方法的默认行为。
示例 10-14 中展示了如何`Summary` trait 的 `summarize` 方法指定一个默认的字符串值,而不是像示例 10-12 中那样只是定义方法签名:
示例 10-14 中我们`Summary` trait 的 `summarize` 方法指定一个默认的字符串值,而不是像示例 10-12 中那样只是定义方法签名:
<span class="filename">文件名src/lib.rs</span>
@ -70,7 +70,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
<span class="caption">示例 10-14`Summary` trait 的定义,带有一个 `summarize` 方法的默认实现</span>
如果想要对 `NewsArticle` 实例使用这个默认实现,而不是定义一个自己的实现,则可以通过 `impl Summary for NewsArticle {}` 指定一个空的 `impl` 块。
如果想要对 `NewsArticle` 实例使用这个默认实现,可以通过 `impl Summary for NewsArticle {}` 指定一个空的 `impl` 块。
虽然我们不再直接为 `NewsArticle` 定义 `summarize` 方法了,但是我们提供了一个默认实现并且指定 `NewsArticle` 实现 `Summary` trait。因此我们仍然可以对 `NewsArticle` 实例调用 `summarize` 方法,如下所示:
@ -106,9 +106,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结
### trait 作为参数
知道了如何定义 trait 和在类型上实现这些 trait 之后,我们可以探索一下如何使用 trait 来接受多种不同类型的参数。
例如在示例 10-13 中为 `NewsArticle``Tweet` 类型实现了 `Summary` trait。我们可以定义一个函数 `notify` 来调用其参数 `item` 上的 `summarize` 方法,该参数是实现了 `Summary` trait 的某种类型。为此可以使用 `impl Trait` 语法,像这样:
知道了如何定义 trait 和在类型上实现这些 trait 之后,我们可以探索一下如何使用 trait 来接受多种不同类型的参数。示例 10-13 中为 `NewsArticle``Tweet` 类型实现了 `Summary` trait用其来定义了一个函数 `notify` 来调用其参数 `item` 上的 `summarize` 方法该参数是实现了 `Summary` trait 的某种类型。为此可以使用 `impl Trait` 语法,像这样:
```rust,ignore
{{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-04-traits-as-parameters/src/lib.rs:here}}
@ -128,7 +126,7 @@ pub fn notify<T: Summary>(item: &T) {
这与之前的例子相同不过稍微冗长了一些。trait bound 与泛型参数声明在一起,位于尖括号中的冒号后面。
`impl Trait` 很方便适用于短小的例子。trait bound 则适用于更复杂的场景。例如,可以获取两个实现了 `Summary` 的参数。使用 `impl Trait` 的语法看起来像这样:
`impl Trait` 很方便,适用于短小的例子。更长的 trait bound 则适用于更复杂的场景。例如,可以获取两个实现了 `Summary` 的参数。使用 `impl Trait` 的语法看起来像这样:
```rust,ignore
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
@ -261,8 +259,6 @@ blanket implementation 会出现在 trait 文档的 “Implementers” 部分。
trait 和 trait bound 让我们使用泛型类型参数来减少重复,并仍然能够向编译器明确指定泛型类型需要拥有哪些行为。因为我们向编译器提供了 trait bound 信息它就可以检查代码中所用到的具体类型是否提供了正确的行为。在动态类型语言中如果我们尝试调用一个类型并没有实现的方法会在运行时出现错误。Rust 将这些错误移动到了编译时,甚至在代码能够运行之前就强迫我们修复错误。另外,我们也无需编写运行时检查行为的代码,因为在编译时就已经检查过了,这样相比其他那些不愿放弃泛型灵活性的语言有更好的性能。
这里还有一种泛型,我们一直在使用它甚至都没有察觉它的存在,这就是 **生命周期***lifetimes*)。不同于其他泛型帮助我们确保类型拥有期望的行为,生命周期则有助于确保引用在我们需要他们的时候一直有效。让我们学习生命周期是如何做到这些的。
[stack-only-data-copy]:
ch04-01-what-is-ownership.html#只在栈上的数据拷贝
[using-trait-objects-that-allow-for-values-of-different-types]:

Loading…
Cancel
Save