From 90ea091397768c654d5f1696837b9514936a1f6b Mon Sep 17 00:00:00 2001 From: kazeno Date: Mon, 19 May 2025 16:23:21 +0800 Subject: [PATCH] update ch10-03 --- src/ch10-03-lifetime-syntax.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/ch10-03-lifetime-syntax.md b/src/ch10-03-lifetime-syntax.md index a61a3f6..fec215b 100644 --- a/src/ch10-03-lifetime-syntax.md +++ b/src/ch10-03-lifetime-syntax.md @@ -103,10 +103,6 @@ Rust 编译器有一个**借用检查器**(*borrow checker*),它比较作 单个的生命周期注解本身没有多少意义,因为生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系的。让我们在 `longest` 函数的上下文中理解生命周期注解如何相互联系。 - - -例如如果函数有一个生命周期 `'a` 的 `i32` 的引用的参数 `first`。还有另一个同样是生命周期 `'a` 的 `i32` 的引用的参数 `second`。这两个生命周期注解意味着引用 `first` 和 `second` 必须与这泛型生命周期存在得一样久。 - ### 函数签名中的生命周期注解 为了在函数签名中使用生命周期注解,需要在函数名和参数列表间的尖括号中声明泛型生命周期(*lifetime*)参数,就像泛型类型(*type*)参数一样。 @@ -153,7 +149,7 @@ Rust 编译器有一个**借用检查器**(*borrow checker*),它比较作 示例 10-23:尝试在 `string2` 离开作用域之后使用 `result` -如果尝试编译会出现如下错误: +如果尝试编译这段代码会出现如下错误: ```console {{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-23/output.txt}} @@ -161,7 +157,7 @@ Rust 编译器有一个**借用检查器**(*borrow checker*),它比较作 错误表明为了保证 `println!` 中的 `result` 是有效的,`string2` 需要直到外部作用域结束都是有效的。Rust 知道这些是因为(`longest`)函数的参数和返回值都使用了相同的生命周期参数 `'a`。 -如果从人的角度读上述代码,我们可能会觉得这个代码是正确的。 `string1` 更长,因此 `result` 会包含指向 `string1` 的引用。因为 `string1` 尚未离开作用域,对于 `println!` 来说 `string1` 的引用仍然是有效的。然而,我们通过生命周期参数告诉 Rust 的是: `longest` 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致。因此,借用检查器不允许示例 10-23 中的代码,因为它可能会存在无效的引用。 +作为人类,我们可以直观地发现 `string1` 比 `string2` 更长,因此 `result` 会包含指向 `string1` 的引用。因为 `string1` 尚未离开作用域,对于 `println!` 来说 `string1` 的引用仍然是有效的。然而,编译器并不能识别出这种情况。我们通过生命周期参数告诉 Rust 的是: `longest` 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致。因此,借用检查器不允许示例 10-23 中的代码,因为它可能会存在无效的引用。 请尝试更多采用不同的值和不同生命周期的引用作为 `longest` 函数的参数和返回值的实验。并在开始编译前猜想你的实验能否通过借用检查器,接着编译一下看看你的理解是否正确! @@ -177,7 +173,7 @@ Rust 编译器有一个**借用检查器**(*borrow checker*),它比较作 我们为参数 `x` 和返回值指定了生命周期参数 `'a`,不过没有为参数 `y` 指定,因为 `y` 的生命周期与参数 `x` 和返回值的生命周期没有任何关系。 -当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用 **没有** 指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值。然而它将会是一个悬垂引用,因为它将会在函数结束时离开作用域。尝试考虑这个并不能编译的 `longest` 函数实现: +当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用**没有**指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值。然而它将会是一个悬垂引用,因为它将会在函数结束时离开作用域。尝试考虑这个并不能编译的 `longest` 函数实现: 文件名:src/main.rs @@ -207,13 +203,13 @@ Rust 编译器有一个**借用检查器**(*borrow checker*),它比较作 示例 10-24:一个存放引用的结构体,所以其定义需要生命周期注解 -这个结构体有唯一一个字段 `part`,它存放了一个字符串 slice,这是一个引用。类似于泛型参数类型,必须在结构体名称后面的尖括号中声明泛型生命周期参数,以便在结构体定义中使用生命周期参数。这个注解意味着 `ImportantExcerpt` 的实例不能比其 `part` 字段中的引用存在的更久。 +这个结构体只有一个字段 `part`,它存放了一个字符串 slice,这是一个引用。类似于泛型参数类型,必须在结构体名称后面的尖括号中声明泛型生命周期参数,以便在结构体定义中使用生命周期参数。这个注解意味着 `ImportantExcerpt` 的实例不能比其 `part` 字段中的引用存在的更久。 这里的 `main` 函数创建了一个 `ImportantExcerpt` 的实例,它存放了变量 `novel` 所拥有的 `String` 的第一个句子的引用。`novel` 的数据在 `ImportantExcerpt` 实例创建之前就存在。另外,直到 `ImportantExcerpt` 离开作用域之后 `novel` 都不会离开作用域,所以 `ImportantExcerpt` 实例中的引用是有效的。 ### 生命周期省略(Lifetime Elision) -现在我们已经知道了每一个引用都有一个生命周期,而且我们需要为那些使用了引用的函数或结构体指定生命周期。然而,第四章的示例 4-9 中有一个函数,如示例 10-25 所示,它没有生命周期注解却能编译成功: +现在我们已经知道了每一个引用都有一个生命周期,而且我们需要为那些使用了引用的函数或结构体指定生命周期。然而,第四章的示例 4-9 中有一个函数,再次展示为如示例 10-25 所示,它没有生命周期注解却能编译成功: 文件名:src/lib.rs @@ -231,11 +227,11 @@ fn first_word<'a>(s: &'a str) -> &'a str { 在编写了很多 Rust 代码后,Rust 团队发现在特定情况下 Rust 程序员们总是重复地编写一模一样的生命周期注解。这些场景是可预测的并且遵循几个明确的模式。接着 Rust 团队就把这些模式编码进了 Rust 编译器中,如此借用检查器在这些情况下就能推断出生命周期而不再强制程序员显式的增加注解。 -这里我们提到一些 Rust 的历史是因为更多的明确的模式被合并和添加到编译器中是完全可能的。未来只会需要更少的生命周期注解。 +这里我们提到一些 Rust 的历史是因为更多的明确的模式被合并和添加到编译器中是完全可能的。未来可能只需要更少的生命周期注解。 被编码进 Rust 引用分析的模式被称为 **生命周期省略规则**(*lifetime elision rules*)。这并不是需要程序员遵守的规则;这些规则是一系列特定的场景,此时编译器会考虑,如果代码符合这些场景,就无需明确指定生命周期。 -省略规则并不提供完整的推断:如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么。编译器会在可以通过增加生命周期注解来解决错误问题的地方给出一个错误提示,而不是进行推断或猜测。 +省略规则并不提供完整的推断。如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么。编译器会在可以通过增加生命周期注解来解决错误问题的地方给出一个错误提示,而不是进行推断或猜测。 函数或方法的参数的生命周期被称为 **输入生命周期**(*input lifetimes*),而返回值的生命周期被称为 **输出生命周期**(*output lifetimes*)。 @@ -245,7 +241,7 @@ fn first_word<'a>(s: &'a str) -> &'a str { 第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:`fn foo<'a>(x: &'a i32) -> &'a i32`。 -第三条规则是如果方法有多个输入生命周期参数并且其中一个参数是 `&self` 或 `&mut self`,说明是个对象的方法 (method)(译者注:这里涉及 rust 的面向对象参见 17 章),那么所有输出生命周期参数被赋予 `self` 的生命周期。第三条规则使得方法更容易读写,因为只需更少的符号。 +第三条规则是如果方法有多个输入生命周期参数并且其中一个参数是 `&self` 或 `&mut self`,说明这是个方法,那么所有输出生命周期参数被赋予 `self` 的生命周期。第三条规则使得方法更容易读写,因为只需更少的符号。 假设我们自己就是编译器。并应用这些规则来计算示例 10-25 中 `first_word` 函数签名中的引用的生命周期。开始时签名中的引用并没有关联任何生命周期: