From d19748f0fc031512b5966ad1caf295b5d6e549cf Mon Sep 17 00:00:00 2001 From: KaiserY Date: Sun, 18 May 2025 22:58:21 +0800 Subject: [PATCH] wip: 2024 edition --- src/ch05-02-example-structs.md | 6 ++-- src/ch05-03-method-syntax.md | 11 ++++---- src/ch10-00-generics.md | 19 ++++++------- src/ch10-01-syntax.md | 21 +++++++------- src/ch10-02-traits.md | 51 +++++++++++++++++----------------- src/ch10-03-lifetime-syntax.md | 15 +++++----- 6 files changed, 59 insertions(+), 64 deletions(-) diff --git a/src/ch05-02-example-structs.md b/src/ch05-02-example-structs.md index 7e798a9..ecb5b0d 100644 --- a/src/ch05-02-example-structs.md +++ b/src/ch05-02-example-structs.md @@ -43,7 +43,7 @@ 示例 5-9:使用元组来指定长方形的宽高 -在某种程度上说,这个程序更好一点了。元组帮助我们增加了一些结构性,并且现在只需传一个参数。不过在另一方面,这个版本却有一点不明确了:元组并没有给出元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分: +在某种程度上说,这个程序更好一点了。元组帮助我们增加了一些结构性,并且现在只需传一个参数。不过在另一方面,这个版本却有一点不明确了:元组并没有给出元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分。 在计算面积时将宽和高弄混倒无关紧要,不过当在屏幕上绘制长方形时就有问题了!我们必须牢记 `width` 的元组索引是 `0`,`height` 的元组索引是 `1`。如果其他人要使用这些代码,他们必须要搞清楚这一点,并也要牢记于心。很容易忘记或者混淆这些值而造成错误,因为我们没有在代码中传达数据的意图。 @@ -63,7 +63,7 @@ 函数 `area` 现在被定义为接收一个名叫 `rectangle` 的参数,其类型是一个结构体 `Rectangle` 实例的不可变借用。第四章讲到过,我们希望借用结构体而不是获取它的所有权,这样 `main` 函数就可以保持 `rect1` 的所有权并继续使用它,所以这就是为什么在函数签名和调用的地方会有 `&`。 -`area` 函数访问 `Rectangle` 实例的 `width` 和 `height` 字段(注意,访问对结构体的引用的字段不会移动字段的所有权,这就是为什么你经常看到对结构体的引用)。`area` 的函数签名现在明确的阐述了我们的意图:使用 `Rectangle` 的 `width` 和 `height` 字段,计算 `Rectangle` 的面积。这表明宽高是相互联系的,并为这些值提供了描述性的名称而不是使用元组的索引值 `0` 和 `1` 。结构体胜在更清晰明了。 +`area` 函数访问 `Rectangle` 实例的 `width` 和 `height` 字段(注意,访问对结构体的引用的字段不会移动字段的所有权,这就是为什么你经常看到对结构体的引用)。`area` 的函数签名现在明确的阐述了我们的意图:使用 `Rectangle` 的 `width` 和 `height` 字段,计算 `Rectangle` 的面积。这表明宽高是相互联系的,并为这些值提供了描述性的名称而不是使用元组的索引值 `0` 和 `1` 。这在可读性上是一个明显的提升。 ### 通过派生 trait 增加实用功能 @@ -147,7 +147,7 @@ Rust **确实** 包含了打印出调试信息的功能,不过我们必须为 除了 `Debug` trait,Rust 还为我们提供了很多可以通过 `derive` 属性来使用的 trait,它们可以为我们的自定义类型增加实用的行为。[附录 C][app-c] 中列出了这些 trait 和行为。第十章会介绍如何通过自定义行为来实现这些 trait,同时还有如何创建你自己的 trait。除了 `derive` 之外,还有很多属性;更多信息请参见 [Rust Reference][attributes] 的 Attributes 部分。 -我们的 `area` 函数是非常特殊的,它只计算长方形的面积。如果这个行为与 `Rectangle` 结构体再结合得更紧密一些就更好了,因为它不能用于其他类型。现在让我们看看如何继续重构这些代码,来将 `area` 函数协调进 `Rectangle` 类型定义的 `area` **方法** 中。 +我们的 `area` 函数用途非常专一:它仅计算长方形的面积。如果这个行为与 `Rectangle` 结构体再结合得更紧密一些就更好了,因为它不能用于其他类型。现在让我们看看如何继续重构这些代码,来将 `area` 函数协调进 `Rectangle` 类型定义的 `area` **方法** 中。 [the-tuple-type]: ch03-02-data-types.html#元组类型 [app-c]: appendix-03-derivable-traits.html diff --git a/src/ch05-03-method-syntax.md b/src/ch05-03-method-syntax.md index 9d16535..bc87e5f 100644 --- a/src/ch05-03-method-syntax.md +++ b/src/ch05-03-method-syntax.md @@ -1,8 +1,7 @@ ## 方法语法 -> [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch05-03-method-syntax.md) ->
-> commit d339373a838fd312a8a9bcc9487e1ffbc9e1582f + + **方法**(method)与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在[第六章][enums]和[第十八章][trait-objects]讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。 @@ -112,13 +111,13 @@ Can rect1 hold rect3? false {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-03-associated-functions/src/main.rs:here}} ``` -关键字 `Self` 在函数的返回类型中代指在 `impl` 关键字后出现的类型,在这里是 `Rectangle` +关键字 `Self` 在函数的返回类型和函数体中,都是对 `impl` 关键字后所示类型的别名,这里是 `Rectangle`。 -使用结构体名和 `::` 语法来调用这个关联函数:比如 `let sq = Rectangle::square(3);`。这个函数位于结构体的命名空间中:`::` 语法用于关联函数和模块创建的命名空间。[第七章][modules]会讲到模块。 +要调用这个关联函数,我们使用结构体名和 `::` 语法;比如 `let sq = Rectangle::square(3);`。这个函数位于结构体的命名空间中:`::` 语法用于关联函数和模块创建的命名空间。[第七章][modules]会讲到模块。 ### 多个 `impl` 块 -每个结构体都允许拥有多个 `impl` 块。例如,示例 5-16 中的代码等同于示例 5-15,但每个方法有其自己的 `impl` 块。 +每个结构体都允许拥有多个 `impl` 块。例如,示例 5-15 中的代码等同于示例 5-16 中所示的代码,但后者每个方法有其自己的 `impl` 块。 ```rust {{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-16/src/main.rs:here}} diff --git a/src/ch10-00-generics.md b/src/ch10-00-generics.md index f261c11..4df7e2e 100644 --- a/src/ch10-00-generics.md +++ b/src/ch10-00-generics.md @@ -1,22 +1,21 @@ # 泛型、Trait 和生命周期 -> [ch10-00-generics.md](https://github.com/rust-lang/book/blob/main/src/ch10-00-generics.md) ->
-> commit 4aa96a3d20570f868bd20e8e3e865b047284be30 + + -每一个编程语言都有高效处理重复概念的工具。在 Rust 中其工具之一就是 **泛型**(*generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如它们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道它们在这里实际上代表什么。 +每一个编程语言都有高效处理重复概念的工具。在 Rust 中其工具之一就是 **泛型**(*generics*):具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如它们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道它们在这里实际上代表什么。 函数可以获取一些不同于 `i32` 或 `String` 这样具体类型的泛型参数,就像一个获取未知类型值的函数可以对多种具体类型的值运行同一段代码一样。事实上我们已经使用过第六章的 `Option`,第八章的 `Vec` 和 `HashMap`,以及第九章的 `Result` 这些泛型了。本章会探索如何使用泛型定义我们自己的类型、函数和方法! 首先,我们将回顾一下提取函数以减少代码重复的机制。接下来,我们将使用相同的技术,从两个仅参数类型不同的函数中创建一个泛型函数。我们也会讲到结构体和枚举定义中的泛型。 -之后,我们讨论 **trait**,这是一个定义泛型行为的方法。trait 可以与泛型结合来将泛型限制为只接受拥有特定行为的类型,而不是任意类型。 +之后,我们讨论如何使用 **trait** 定义泛型行为的方法。trait 可以与泛型结合来将泛型限制为只接受拥有特定行为的类型,而不是任意类型。 -最后介绍 **生命周期**(*lifetimes*),它是一类允许我们向编译器提供引用如何相互关联的泛型。Rust 的生命周期功能允许在很多场景下借用值的同时仍然使编译器能够检查这些引用的有效性。 +最后介绍 **生命周期**(*lifetimes*):一类允许我们向编译器提供引用如何相互关联的泛型。Rust 的生命周期功能允许在更多场景下借用值的同时仍然使编译器能够检查这些引用的有效性而不用借助我们的帮助。 ## 提取函数来减少重复 -泛型允许我们使用一个可以代表多种类型的占位符来替换特定类型,以此来减少代码冗余。在深入了解泛型的语法之前,我们首先来看一种没有使用泛型的减少冗余的方法,即提取一个函数。在这个函数中,我们用一个可以代表多种值的占位符来替换具体的值。接着我们使用相同的技术来提取一个泛型函数!!通过学习如何识别并提取可以整合进一个函数的重复代码,你也会开始识别出可以使用泛型的重复代码。 +泛型允许我们使用一个可以代表多种类型的占位符来替换特定类型,以此来减少代码冗余。在深入了解泛型的语法之前,我们首先来看一种没有使用泛型的减少冗余的方法,即提取一个函数。在这个函数中,我们用一个可以代表多种值的占位符来替换具体的值。接着我们使用相同的技术来提取一个泛型函数!通过学习如何识别并提取可以整合进一个函数的重复代码,你也会开始识别出可以使用泛型的重复代码。 让我们从下面这个寻找列表中最大值的小程序开始,如示例 10-1 所示: @@ -28,7 +27,7 @@ 示例 10-1:在一个数字列表中寻找最大值的函数 -这段代码获取一个整型列表,存放在变量 `number_list` 中。它将列表的第一个数字的引用放入了变量 `largest` 中。接着遍历了列表中的所有数字,如果当前值大于 `largest` 中储存的值,将 `largest` 替换为这个值。如果当前值小于或者等于目前为止的最大值,`largest` 保持不变。当列表中所有值都被考虑到之后,`largest` 将会指向最大值,在这里也就是 100。 +这段代码获取一个整型列表,存放在变量 `number_list` 中。它将列表的第一个数字的引用放入了变量 `largest` 中。接着遍历了列表中的所有数字,如果当前值大于 `largest` 中储存的值,将 `largest` 替换为这个值。如果当前值小于或者等于目前为止的最大值,变量保持不变,并且代码移动到列表中的下一个数字。当列表中所有值都被考虑到之后,`largest` 将会指向最大值,在这里也就是 100。 我们的任务是在两个不同的数字列表中寻找最大值。为此我们可以选择重复示例 10-1 中的代码在程序的两个不同位置使用相同的逻辑,如示例 10-2 所示: @@ -38,7 +37,7 @@ {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-02/src/main.rs}} ``` -示例 10-2:寻找 **两个** 数字列表最大值的代码 +示例 10-2:寻找**两个**数组最大值的代码 虽然代码能够执行,但是重复的代码是冗余且容易出错的,更新逻辑时我们不得不记住需要修改多处地方的代码。 @@ -56,7 +55,7 @@ `largest` 函数有一个参数 `list`,它代表会传递给函数的任何具体的 `i32`值的 slice。函数定义中的 `list` 代表任何 `&[i32]`。当调用 `largest` 函数时,其代码实际上运行于我们传递的特定值上。 -总的来说,从示例 10-2 到示例 10-3 中涉及的机制经历了如下几步: +总的来说,从示例 10-2 到示例 10-3 中代码的修改经历了如下几步: 1. 找出重复代码。 2. 将重复代码提取到了一个函数中,并在函数签名中指定了代码中的输入和返回值。 diff --git a/src/ch10-01-syntax.md b/src/ch10-01-syntax.md index 6adfa80..7fe27a0 100644 --- a/src/ch10-01-syntax.md +++ b/src/ch10-01-syntax.md @@ -1,8 +1,7 @@ ## 泛型数据类型 -> [ch10-01-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch10-01-syntax.md) ->
-> commit f2a78f64b668f63f581203c6bac509903f7c00ee + + 我们可以使用泛型为像函数签名或结构体这样的项创建定义,这样它们就可以用于多种不同的具体数据类型。让我们看看如何使用泛型定义函数、结构体、枚举和方法,然后我们将讨论泛型如何影响代码性能。 @@ -32,7 +31,7 @@ fn largest(list: &[T]) -> &T { 可以这样理解这个定义:函数 `largest` 有泛型类型 `T`。它有个参数 `list`,其类型是元素为 `T` 的 slice。`largest` 函数会返回一个与 `T` 相同类型的引用。 -示例 10-5 中的 `largest` 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 `largest` 函数,把 `i32` 值的 slice 或 `char` 值的 slice 传给它。请注意这些代码还不能编译,不过稍后在本章会解决这个问题。 +示例 10-5 中的 `largest` 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 `largest` 函数,把 `i32` 值的 slice 或 `char` 值的 slice 传给它。请注意这些代码还不能编译。 文件名:src/main.rs @@ -42,7 +41,7 @@ fn largest(list: &[T]) -> &T { 示例 10-5:一个使用泛型参数的 `largest` 函数定义,尚不能编译 -如果现在就编译这个代码,会出现如下错误: +如果现在就编译这段代码,会出现如下错误: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt}} @@ -62,7 +61,7 @@ fn largest(list: &[T]) -> &T { 示例 10-6:`Point` 结构体存放了两个 `T` 类型的值 `x` 和 `y` -其语法类似于函数定义中使用泛型。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。 +在结构体定义中使用泛型的语法类似于函数定义中使用泛型。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。 注意 `Point` 的定义中只使用了一个泛型类型,这个定义表明结构体 `Point` 对于一些类型 `T` 是泛型的,而且字段 `x` 和 `y` **都是** 相同类型的,无论它具体是何类型。如果尝试创建一个有不同类型值的 `Point` 的实例,像示例 10-7 中的代码就不能编译: @@ -74,7 +73,7 @@ fn largest(list: &[T]) -> &T { 示例 10-7:字段 `x` 和 `y` 的类型必须相同,因为它们都有相同的泛型类型 `T` -在这个例子中,当把整型值 5 赋值给 `x` 时,就告诉了编译器这个 `Point` 实例中的泛型 `T` 全是整型。接着指定 `y` 为浮点值 4.0,因为它`y`被定义为与 `x` 相同类型,所以将会得到一个像这样的类型不匹配错误: +在这个例子中,当把整型值 `5` 赋值给 `x` 时,就告诉了编译器这个 `Point` 实例中的泛型 `T` 全是整型。接着指定 `y` 为浮点值 `4.0`,因为 `y` 被定义为与 `x` 相同类型,所以将会得到一个像这样的类型不匹配错误: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-07/output.txt}} @@ -130,7 +129,7 @@ enum Result { 示例 10-9:在 `Point` 结构体上实现方法 `x`,它返回 `T` 类型的字段 `x` 的引用 -这里在 `Point` 上定义了一个叫做 `x` 的方法来返回字段 `x` 中数据的引用: +这里在 `Point` 上定义了一个叫做 `x` 的方法用于返回字段 `x` 中数据的引用。 注意必须在 `impl` 后面声明 `T`,这样就可以在 `Point` 上实现的方法中使用 `T` 了。通过在 `impl` 之后声明泛型 `T`,Rust 就知道 `Point` 的尖括号中的类型是泛型而不是具体类型。我们可以为泛型参数选择一个与结构体定义中声明的泛型参数所不同的名称,不过依照惯例使用了相同的名称。在声明泛型类型参数的 `impl` 中编写的方法将会定义在该类型的任何实例上,无论最终替换泛型类型参数的是何具体类型。(译者注:以示例 10-9 为例,`impl` 中声明了泛型类型参数 `T`,`x` 是编写在 `impl` 中的方法,`x` 方法将会定义在 `Point` 的任何实例上,无论最终替换泛型类型参数 `T` 的是何具体类型)。 @@ -162,13 +161,13 @@ enum Result { ### 泛型代码的性能 -在阅读本部分内容的同时,你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是泛型并不会使程序比具体类型运行得慢。 +你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是泛型并不会使程序比具体类型运行得慢。 -Rust 通过在编译时进行泛型代码的 **单态化**(*monomorphization*)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。 +Rust 通过在编译时进行泛型代码的**单态化**(*monomorphization*)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。 在这个过程中,编译器所做的工作正好与示例 10-5 中我们创建泛型函数的步骤相反。编译器寻找所有泛型代码被调用的位置并使用泛型代码针对具体类型生成代码。 -让我们看看这如何用于标准库中的 `Option` 枚举: +让我们看看这在标准库的 `Option` 枚举上是如何工作的: ```rust let integer = Some(5); diff --git a/src/ch10-02-traits.md b/src/ch10-02-traits.md index 0ebb131..568e73e 100644 --- a/src/ch10-02-traits.md +++ b/src/ch10-02-traits.md @@ -1,8 +1,7 @@ ## Trait:定义共同行为 -> [ch10-02-traits.md](https://github.com/rust-lang/book/blob/main/src/ch10-02-traits.md) ->
-> commit 92bfbfacf88ee9a814cea0a58e9c019c529ef4ae + + *trait* 定义了某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共同行为。可以使用 *trait bounds* 指定泛型是任何拥有特定行为的类型。 @@ -12,9 +11,9 @@ 一个类型的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。 -例如,这里有多个存放了不同类型和属性文本的结构体:结构体 `NewsArticle` 用于存放发生于世界各地的新闻故事,而结构体 `Tweet` 最多只能存放 280 个字符的内容,以及像是否转推或是否是对推友的回复这样的元数据。 +例如,这里有多个存放了不同类型和属性文本的结构体:结构体 `NewsArticle` 用于存放发生于世界各地的新闻故事,而结构体 `SocialPost` 最多只能存放 280 个字符的内容,以及指示该帖子是新发布的、转发的还是对另一条帖子的回复的元数据。 -我们想要创建一个名为 `aggregator` 的多媒体聚合库用来显示可能储存在 `NewsArticle` 或 `Tweet` 实例中的数据摘要。为了实现功能,每个结构体都要能够获取摘要,这样的话就可以调用实例的 `summarize` 方法来请求摘要。示例 10-12 中展示了一个表现这个概念的公有 `Summary` trait 的定义: +我们想要创建一个名为 `aggregator` 的多媒体聚合库用来显示可能储存在 `NewsArticle` 或 `SocialPost` 实例中的数据摘要。为了实现功能,每个结构体都要能够获取摘要,这样的话就可以调用实例的 `summarize` 方法来请求摘要。示例 10-12 中展示了一个表现这个概念的公有 `Summary` trait 的定义: 文件名:src/lib.rs @@ -24,7 +23,7 @@ 示例 10-12:`Summary` trait 定义,它包含由 `summarize` 方法提供的行为 -这里使用 `trait` 关键字来声明一个 trait,后面是 trait 的名字,在这个例子中是 `Summary`。我们也声明 `trait` 为 `pub` 以便依赖这个 crate 的 crate 也可以使用这个 trait,正如我们见过的一些示例一样。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名,在这个例子中是 `fn summarize(&self) -> String`。 +这里使用 `trait` 关键字来声明一个 trait,后面是 trait 的名字,在这个例子中是 `Summary`。我们也声明 `trait` 为 `pub` 以便依赖这个 crate 的其它 crate 也可以使用这个 trait,正如我们见过的一些示例一样。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名,在这个例子中是 `fn summarize(&self) -> String`。 在方法签名后跟分号,而不是在大括号中提供其实现。接着每一个实现这个 trait 的类型都需要提供其自定义行为的方法体,编译器也会确保任何实现 `Summary` trait 的类型都拥有与这个签名的定义完全一致的 `summarize` 方法。 @@ -32,7 +31,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结 ### 为类型实现 trait -现在我们定义了 `Summary` trait 的签名,接着就可以在多媒体聚合库中实现这个类型了。示例 10-13 中展示了 `NewsArticle` 结构体上 `Summary` trait 的一个实现,它使用标题、作者和创建的位置作为 `summarize` 的返回值。对于 `Tweet` 结构体,我们选择将 `summarize` 定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 280 字符以内。 +现在我们定义了 `Summary` trait 的签名,接着就可以在多媒体聚合库中实现这个类型了。示例 10-13 中展示了 `NewsArticle` 结构体上 `Summary` trait 的一个实现,它使用标题、作者和创建的位置作为 `summarize` 的返回值。对于 `SocialPost` 结构体,我们选择将 `summarize` 定义为用户名后跟帖子全文作为返回值,并假设帖子内容已经被限制为 280 字符以内。 文件名:src/lib.rs @@ -40,21 +39,21 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结 {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-13/src/lib.rs:here}} ``` -示例 10-13:在 `NewsArticle` 和 `Tweet` 类型上实现 `Summary` trait +示例 10-13:在 `NewsArticle` 和 `SocialPost` 类型上实现 `Summary` trait 在类型上实现 trait 类似于实现常规方法。区别在于 `impl` 关键字之后,我们提供需要实现 trait 的名称,接着是 `for` 和需要实现 trait 的类型的名称。在 `impl` 块中,使用 trait 定义中的方法签名,不过不再后跟分号,而是需要在大括号中编写函数体来为特定类型实现 trait 方法所拥有的行为。 -现在库在 `NewsArticle` 和 `Tweet` 上实现了`Summary` trait,crate 的用户可以像调用常规方法一样调用 `NewsArticle` 和 `Tweet` 实例的 trait 方法了。唯一的区别是 trait 必须和类型一起引入作用域以便使用额外的 trait 方法。这是一个二进制 crate 如何利用 `aggregator` 库 crate 的例子: +现在库在 `NewsArticle` 和 `SocialPost` 上实现了`Summary` trait,crate 的用户可以像调用常规方法一样调用 `NewsArticle` 和 `SocialPost` 实例的 trait 方法了。唯一的区别是 trait 必须和类型一起引入作用域以便使用额外的 trait 方法。这是一个二进制 crate 如何利用 `aggregator` 库 crate 的例子: ```rust,ignore {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-01-calling-trait-method/src/main.rs}} ``` -这会打印出 `1 new tweet: horse_ebooks: of course, as you probably already know, people`。 +这会打印出 `1 new post: horse_ebooks: of course, as you probably already know, people`。 -其他依赖 `aggregator` crate 的 crate 也可以将 `Summary` 引入作用域以便为其自己的类型实现该 trait。需要注意的限制是,只有在 trait 或类型至少有一个属于当前 crate 时,我们才能对类型实现该 trait。例如,可以为 `aggregator` crate 的自定义类型 `Tweet` 实现如标准库中的 `Display` trait,这是因为 `Tweet` 类型位于 `aggregator` crate 本地的作用域中。类似地,也可以在 `aggregator` crate 中为 `Vec` 实现 `Summary`,这是因为 `Summary` trait 位于 `aggregator` crate 本地作用域中。 +其他依赖 `aggregator` crate 的 crate 也可以将 `Summary` 引入作用域以便为其自己的类型实现该 trait。需要注意的限制是,只有在 trait 或类型至少有一个属于当前 crate 时,我们才能对类型实现该 trait。例如,可以为 `aggregator` crate 的自定义类型 `SocialPost` 实现如标准库中的 `Display` trait,这是因为 `SocialPost` 类型位于 `aggregator` crate 本地的作用域中。类似地,也可以在 `aggregator` crate 中为 `Vec` 实现 `Summary`,这是因为 `Summary` trait 位于 `aggregator` crate 本地作用域中。 -但是不能为外部类型实现外部 trait。例如,不能在 `aggregator` crate 中为 `Vec` 实现 `Display` trait。这是因为 `Display` 和 `Vec` 都定义于标准库中,它们并不位于 `aggregator` crate 本地作用域中。这个限制是被称为 **相干性**(*coherence*)的程序属性的一部分,或者更具体的说是 **孤儿规则**(*orphan rule*),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait,而 Rust 将无从得知应该使用哪一个实现。 +但是不能为外部类型实现外部 trait。例如,不能在 `aggregator` crate 中为 `Vec` 实现 `Display` trait。这是因为 `Display` 和 `Vec` 都定义于标准库中,它们并不位于 `aggregator` crate 本地作用域中。这个限制是被称为**相干性**(*coherence*)的程序属性的一部分,或者更具体的说是 **孤儿规则**(*orphan rule*),其得名于不存在父类型。这条规则确保了其他人编写的代码不会破坏你的代码,反之亦然。没有这条规则的话,两个 crate 可以分别对相同类型实现相同的 trait,而 Rust 将无从得知应该使用哪一个实现。 ### 默认实现 @@ -80,7 +79,7 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结 这段代码会打印 `New article available! (Read more...)`。 -为 `summarize` 创建默认实现并不要求对示例 10-13 中 `Tweet` 上的 `Summary` 实现做任何改变。其原因是重载一个默认实现的语法与实现没有默认实现的 trait 方法的语法一样。 +为 `summarize` 创建默认实现并不要求对示例 10-13 中 `SocialPost` 上的 `Summary` 实现做任何改变。其原因是重载一个默认实现的语法与实现没有默认实现的 trait 方法的语法一样。 默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。如此,trait 可以提供很多有用的功能而只需要实现指定一小部分内容。例如,我们可以定义 `Summary` trait,使其具有一个需要实现的 `summarize_author` 方法,然后定义一个 `summarize` 方法,此方法的默认实现调用 `summarize_author` 方法: @@ -88,31 +87,31 @@ trait 体中可以有多个方法:一行一个方法签名且都以分号结 {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/lib.rs:here}} ``` -为了使用这个版本的 `Summary`,只需在实现 trait 时定义 `summarize_author` 即可: +为了使用这个版本的 `Summary`,只需在为类型实现 trait 时定义 `summarize_author` 即可: ```rust,ignore {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/lib.rs:impl}} ``` -一旦定义了 `summarize_author`,我们就可以对 `Tweet` 结构体的实例调用 `summarize` 了,而 `summarize` 的默认实现会调用我们提供的 `summarize_author` 定义。因为实现了 `summarize_author`,`Summary` trait 就提供了 `summarize` 方法的功能,且无需编写更多的代码。 +一旦定义了 `summarize_author`,我们就可以对 `SocialPost` 结构体的实例调用 `summarize` 了,而 `summarize` 的默认实现会调用我们提供的 `summarize_author` 定义。因为实现了 `summarize_author`,`Summary` trait 就提供了 `summarize` 方法的功能,且无需编写更多的代码。 ```rust,ignore {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-03-default-impl-calls-other-methods/src/main.rs:here}} ``` -这会打印出 `1 new tweet: (Read more from @horse_ebooks...)`。 +这会打印出 `1 new post: (Read more from @horse_ebooks...)`。 注意无法从相同方法的重载实现中调用默认方法。 ### trait 作为参数 -知道了如何定义 trait 和在类型上实现这些 trait 之后,我们可以探索一下如何使用 trait 来接受多种不同类型的参数。示例 10-13 中为 `NewsArticle` 和 `Tweet` 类型实现了 `Summary` trait,用其来定义了一个函数 `notify` 来调用其参数 `item` 上的 `summarize` 方法,该参数是实现了 `Summary` trait 的某种类型。为此可以使用 `impl Trait` 语法,像这样: +知道了如何定义 trait 和在类型上实现这些 trait 之后,我们可以探索一下如何使用 trait 来接受多种不同类型的参数。示例 10-13 中为 `NewsArticle` 和 `SocialPost` 类型实现了 `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}} ``` -对于 `item` 参数,我们指定了 `impl` 关键字和 trait 名称,而不是具体的类型。该参数支持任何实现了指定 trait 的类型。在 `notify` 函数体中,可以调用任何来自 `Summary` trait 的方法,比如 `summarize`。我们可以传递任何 `NewsArticle` 或 `Tweet` 的实例来调用 `notify`。任何用其它如 `String` 或 `i32` 的类型调用该函数的代码都不能编译,因为它们没有实现 `Summary`。 +对于 `item` 参数,我们指定了 `impl` 关键字和 trait 名称,而不是具体的类型。该参数支持任何实现了指定 trait 的类型。在 `notify` 函数体中,可以调用任何来自 `Summary` trait 的方法,比如 `summarize`。我们可以传递任何 `NewsArticle` 或 `SocialPost` 的实例来调用 `notify`。任何用其它如 `String` 或 `i32` 的类型调用该函数的代码都不能编译,因为它们没有实现 `Summary`。 #### Trait Bound 语法 @@ -124,7 +123,7 @@ pub fn notify(item: &T) { } ``` -这与之前的例子相同,不过稍微冗长了一些。trait bound 与泛型参数声明在一起,位于尖括号中的冒号后面。 +这种更冗长的写法与上一节的示例等价,但更为冗长。trait bound 与泛型参数声明在一起,位于尖括号中的冒号后面。 `impl Trait` 很方便,适用于短小的例子。更长的 trait bound 则适用于更复杂的场景。例如,可以获取两个实现了 `Summary` 的参数。使用 `impl Trait` 的语法看起来像这样: @@ -132,7 +131,7 @@ pub fn notify(item: &T) { pub fn notify(item1: &impl Summary, item2: &impl Summary) { ``` -这适用于 `item1` 和 `item2` 允许是不同类型的情况(只要它们都实现了 `Summary`)。不过如果你希望强制它们都是相同类型呢?这只有在使用 trait bound 时才有可能: +这适用于 `item1` 和 `item2` 允许是不同类型的情况(只要它们都实现了 `Summary`)。不过如果你希望强制它们都是相同类型呢?但如果我们希望强制两个参数必须具有相同类型,则必须使用 trait bound,如下所示: ```rust,ignore pub fn notify(item1: &T, item2: &T) { @@ -142,7 +141,7 @@ pub fn notify(item1: &T, item2: &T) { #### 通过 `+` 指定多个 trait bound -如果 `notify` 需要显示 `item` 的格式化形式,同时也要使用 `summarize` 方法,那么 `item` 就需要同时实现两个不同的 trait:`Display` 和 `Summary`。这可以通过 `+` 语法实现: +我们也可以指定多个 trait bound。假设我们希望 `notify` 在 `item` 上既能使用格式化显示,又能使用 `summarize` 方法:在 `notify` 的定义中,指定 `item` 必须同时实现 `Display` 和 `Summary` 两个 trait。这可以通过 `+` 语法实现: ```rust,ignore pub fn notify(item: &(impl Summary + Display)) { @@ -180,17 +179,17 @@ fn some_function(t: &T, u: &U) -> i32 { {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-05-returning-impl-trait/src/lib.rs:here}} ``` -通过使用 `impl Summary` 作为返回值类型,我们指定了 `returns_summarizable` 函数返回某个实现了 `Summary` trait 的类型,但是不确定其具体的类型。在这个例子中 `returns_summarizable` 返回了一个 `Tweet`,不过调用方并不知情。 +通过使用 `impl Summary` 作为返回值类型,我们指定了 `returns_summarizable` 函数返回某个实现了 `Summary` trait 的类型,但是不确定其具体的类型。在这个例子中 `returns_summarizable` 返回了一个 `SocialPost`,不过调用方并不知情。 返回一个只是指定了需要实现的 trait 的类型的能力在闭包和迭代器场景十分的有用,第十三章会介绍它们。闭包和迭代器创建只有编译器知道的类型,或者是非常非常长的类型。`impl Trait` 允许你简单的指定函数返回一个 `Iterator` 而无需写出实际的冗长的类型。 -不过这只适用于返回单一类型的情况。例如,这段代码的返回值类型指定为返回 `impl Summary`,但是返回了 `NewsArticle` 或 `Tweet` 就行不通: +不过这只适用于返回单一类型的情况。例如,这段代码的返回值类型指定为返回 `impl Summary`,但是返回了 `NewsArticle` 或 `SocialPost` 就行不通: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/no-listing-06-impl-trait-returns-one-type/src/lib.rs:here}} ``` -这里尝试返回 `NewsArticle` 或 `Tweet`。这不能编译,因为 `impl Trait` 工作方式的限制。第十八章的 [“顾及不同类型值的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types] 部分会介绍如何编写这样一个函数。 +这里尝试返回 `NewsArticle` 或 `SocialPost` 是不被允许的,原因在于编译器中 `impl Trait` 语法的实现限制。。第十八章的 [“顾及不同类型值的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types] 部分会介绍如何编写这样一个函数。 ### 使用 trait bound 有条件地实现方法 @@ -216,9 +215,9 @@ impl ToString for T { let s = 3.to_string(); ``` -blanket implementation 会出现在 trait 文档的 “Implementers” 部分。 +Blanket implementation 会出现在 trait 文档的 “Implementers” 部分。 -trait 和 trait bound 让我们能够使用泛型类型参数来减少重复,而且能够向编译器明确指定泛型类型需要拥有哪些行为。然后编译器可以利用 trait bound 信息检查代码中所用到的具体类型是否提供了正确的行为。在动态类型语言中,如果我们调用了一个未定义的方法,会在运行时出现错误。Rust 将这些错误移动到了编译时,甚至在代码能够运行之前就强迫我们修复问题。另外,我们也无需编写运行时检查行为的代码,因为在编译时就已经检查过了。这样既提升了性能又不必放弃泛型的灵活性。 +Trait 和 trait bound 让我们能够使用泛型类型参数来减少重复,而且能够向编译器明确指定泛型类型需要拥有哪些行为。然后编译器可以利用 trait bound 信息检查代码中所用到的具体类型是否提供了正确的行为。在动态类型语言中,如果我们调用了一个未定义的方法,会在运行时出现错误。Rust 将这些错误移动到了编译时,甚至在代码能够运行之前就强迫我们修复问题。另外,我们也无需编写运行时检查行为的代码,因为在编译时就已经检查过了。这样既提升了性能又不必放弃泛型的灵活性。 [using-trait-objects-that-allow-for-values-of-different-types]: ch18-02-trait-objects.html#顾及不同类型值的-trait-对象 diff --git a/src/ch10-03-lifetime-syntax.md b/src/ch10-03-lifetime-syntax.md index 5ef5cfa..a61a3f6 100644 --- a/src/ch10-03-lifetime-syntax.md +++ b/src/ch10-03-lifetime-syntax.md @@ -1,12 +1,11 @@ ## 生命周期确保引用有效 -> [ch10-03-lifetime-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch10-03-lifetime-syntax.md) ->
-> commit 5f67eee42345ba44f6f08a22c2192165f4b0e930 + + -生命周期是另一类我们已经使用过的泛型。不同于确保类型有期望的行为,生命周期确保引用如预期一直有效。 +生命周期是另一类我们已经使用过的泛型。不同于确保类型有期望的行为,生命周期用于保证引用在我们需要的整个期间内都是有效的。 -当在第四章讨论 [“引用和借用”][references-and-borrowing] 部分时,我们遗漏了一个重要的细节:Rust 中的每一个引用都有其 **生命周期**(*lifetime*),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明它们的关系,这样就能确保运行时实际使用的引用绝对是有效的。 +当在第四章讨论 [“引用和借用”][references-and-borrowing] 部分时,我们遗漏了一个重要的细节:Rust 中的每一个引用都有其**生命周期**(*lifetime*),也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的,正如大部分时候类型也是可以推断的一样。类似于当因为有多种可能类型的时候必须注明类型,也会出现引用的生命周期以一些不同方式相关联的情况,所以 Rust 需要我们使用泛型生命周期参数来注明它们的关系,这样就能确保运行时实际使用的引用绝对是有效的。 生命周期注解甚至不是一个大部分语言都有的概念,所以这可能感觉起来有些陌生。虽然本章不可能涉及到它全部的内容,我们会讲到一些通常你可能会遇到的生命周期语法以便你熟悉这个概念。 @@ -22,7 +21,7 @@ > 注意:示例 10-16、10-17 和 10-23 中声明了没有初始值的变量,所以这些变量存在于外部作用域。这乍看之下好像和 Rust 不允许存在空值相冲突。然而如果尝试在给它一个值之前使用这个变量,会出现一个编译时错误,这就说明了 Rust 确实不允许空值。 -外部作用域声明了一个没有初值的变量 `r`,而内部作用域声明了一个初值为 5 的变量`x`。在内部作用域中,我们尝试将 `r` 的值设置为一个 `x` 的引用。接着在内部作用域结束后,尝试打印出 `r` 的值。这段代码不能编译因为 `r` 引用的值在尝试使用之前就离开了作用域。如下是错误信息: +外部作用域声明了一个没有初值的变量 `r`,而内部作用域声明了一个初值为 `5` 的变量`x`。在内部作用域中,我们尝试将 `r` 的值设置为一个 `x` 的引用。接着在内部作用域结束后,尝试打印出 `r` 的值。这段代码不能编译因为 `r` 引用的值在尝试使用之前就离开了作用域。如下是错误信息: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-16/output.txt}} @@ -32,7 +31,7 @@ #### 借用检查器 -Rust 编译器有一个 **借用检查器**(*borrow checker*),它比较作用域来确保所有的借用都是有效的。示例 10-17 展示了与示例 10-16 相同的例子不过带有变量生命周期的注释: +Rust 编译器有一个**借用检查器**(*borrow checker*),它比较作用域来确保所有的借用都是有效的。示例 10-17 展示了与示例 10-16 相同的例子不过带有变量生命周期的注释: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-17/src/main.rs}} @@ -66,7 +65,7 @@ Rust 编译器有一个 **借用检查器**(*borrow checker*),它比较作 示例 10-19:`main` 函数调用 `longest` 函数来寻找两个字符串 slice 中较长的一个 -注意这个函数获取作为引用的字符串 slice,而不是字符串,因为我们不希望 `longest` 函数获取参数的所有权。参考之前第四章中的 [“字符串 slice 作为参数”][string-slices-as-parameters] 部分中更多关于为什么示例 10-19 的参数正符合我们期望的讨论。 +注意这个函数获取作为引用的字符串 slice,而不是字符串,因为我们不希望 `longest` 函数获取参数的所有权。参考之前第四章中的[“字符串 slice 作为参数”][string-slices-as-parameters]部分中更多关于为什么示例 10-19 的参数正符合我们期望的讨论。 如果尝试像示例 10-20 中那样实现 `longest` 函数,它并不能编译: