@ -209,7 +209,7 @@ fn main() {
在这个例子中,`string1` 直到外部作用域结束都是有效的,`string2` 则在内部作用域中是有效的,而 `result` 则引用了一些直到内部作用域结束都是有效的值。借用检查器认可这些代码;它能够编译和运行,并打印出 `The longest string is long string is long` 。
接下来,让我们尝试一个 `result` 的引用的生命周期肯定比两个参数的要短的例子。将 `result` 变量的声明从内部作用域中移动出来 ,但是将 `result` 和 `string2` 变量的赋值语句一同留在内部作用域里。接下来,我们将使用 `result` 的 `println!` 移动到内部作用域之外,就在其结束之后。注意示例 10-24 中的代码不能 编译:
接下来,让我们尝试另外一个例子,该例子揭示了 `result` 的引用的生命周期必须是两个参数中较短的那个。以下代码将 `result` 变量的声明移动出内部作用域 ,但是将 `result` 和 `string2` 变量的赋值语句一同留在内部作用域中。接着,使用了变量 `result` 的 `println!` 也被移动到内部作用域之外。注意示例 10-24 中的代码不能通过 编译:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -225,7 +225,7 @@ fn main() {
}
```
< span class = "caption" > 示例 10-24: 在 `string2` 离开作用域之后使用 `result` 的尝试不能编译 </ span >
< span class = "caption" > 示例 10-24: 尝试 在 `string2` 离开作用域之后使用 `result` </ span >
如果尝试编译会出现如下错误:
@ -244,13 +244,13 @@ error[E0597]: `string2` does not live long enough
错误表明为了保证 `println!` 中的 `result` 是有效的,`string2` 需要直到外部作用域结束都是有效的。Rust 知道这些是因为(`longest`)函数的参数和返回值都使用了相同的生命周期参数 `'a` 。
以人类的理解 `string1` 更长,因此 `result` 会包含指向 `string1` 的引用。因为 `string1` 尚未离开作用域,对于 `println!` 来说 `string1` 的引用仍然是有效的。然而,我们通过生命周期参数告诉 Rust 的是 `longest` 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致。因此,借用检查器不允许示例 10-24 中的代码,因为它可能会存在无效的引用。
如果从人的角度读上述代码,我们可能会觉得这个代码是正确的。 `string1` 更长,因此 `result` 会包含指向 `string1` 的引用。因为 `string1` 尚未离开作用域,对于 `println!` 来说 `string1` 的引用仍然是有效的。然而,我们通过生命周期参数告诉 Rust 的是: `longest` 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致。因此,借用检查器不允许示例 10-24 中的代码,因为它可能会存在无效的引用。
请尝试更多采用不同的值和不同生命周期的引用作为 `longest` 函数的参数和返回值的实验。并在开始编译前猜想你的实验能否通过借用检查器,接着编译一下看看你的理解是否正确!
### 深入理解生命周期
指定生命周期参数的正确方式依赖函数具体的 功能。例如,如果将 `longest` 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice, 就不需要为参数 `y` 指定一个生命周期。如下代码将能够编译:
指定生命周期参数的正确方式依赖函数实现的 具体功能。例如,如果将 `longest` 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice, 就不需要为参数 `y` 指定一个生命周期。如下代码将能够编译:
< span class = "filename" > 文件名: src/main.rs< / span >
@ -297,11 +297,11 @@ function body at 1:1...
出现的问题是 `result` 在 `longest` 函数的结尾将离开作用域并被清理,而我们尝试从函数返回一个 `result` 的引用。无法指定生命周期参数来改变悬垂引用,而且 Rust 也不允许我们创建一个悬垂引用。在这种情况,最好的解决方案是返回一个有所有权的数据类型而不是一个引用,这样函数调用者就需要负责清理这个值了。
从结果上看,生命周期语法是关于如何联系函数不同参数和返回值的生命周期的。一旦他们形成了某种联系 , Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为。
综上,生命周期语法是用于将函数的多个参数与其返回值的生命周期进行关联的。一旦他们形成了某种关联 , Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为。
### 结构体定义中的生命周期注解
目前为止,我们只定义过有所有权类型的结构体。也可以定义存放引用的结构体,不过 需要为结构体定义中的每一个引用添加生命周期注解。示例 10-25 中有一个存放了一个字符串 slice 的结构体 `ImportantExcerpt` :
目前为止,我们只定义过有所有权类型的结构体。接下来,我们将定义包含引用的结构体,不过这 需要为结构体定义中的每一个引用添加生命周期注解。示例 10-25 中有一个存放了一个字符串 slice 的结构体 `ImportantExcerpt` :
< span class = "filename" > 文件名: src/main.rs< / span >
@ -327,7 +327,7 @@ fn main() {
### 生命周期省略( Lifetime Elision)
在这一部分, 我们知道了每一个引用都有一个生命周期,而且需要为使用了引用的函数或结构体指定生命周期。然而,第四章的示例 4-9 中有一个函数,我们在示例 10-26 中再次展示出来,它没有生命周期注解却能成功编译 :
现 在我们已经 知道了每一个引用都有一个生命周期,而且我们 需要为那些 使用了引用的函数或结构体指定生命周期。然而,第四章的示例 4-9 中有一个函数,如示例 10-26 所示,它没有生命周期注解却能编译成功 :
< span class = "filename" > 文件名: src/lib.rs< / span >
@ -405,7 +405,7 @@ fn longest(x: &str, y: &str) -> &str {
fn longest< 'a, 'b>(x: & 'a str, y: & 'b str) -> & str {
```
再来应用第二条规则,它并不适用 因为存在多于一 个输入生命周期。再来看第三条规则,它同样也不适用因为没有 `self` 参数。然后我们就没有更多规则了,不过还没有计算出返回值的 类型的生命周期。这就是为什么在编译示例 10-21 的代码时会出现错误的原因:编译器使用所有已知的生命周期省略规则,不过 仍不能计算出签名中所有引用的生命周期。
再来应用第二条规则,因为函数 存在多个输入生命周期,它并不适用于这种情况 。再来看第三条规则,它同样也不适用,这是因为没有 `self` 参数。应用了三个规则之后编译器还没有计算出返回值 类型的生命周期。这就是为什么在编译示例 10-21 的代码时会出现错误的原因:编译器使用所有已知的生命周期省略规则,仍不能计算出签名中所有引用的生命周期。
因为第三条规则真正能够适用的就只有方法签名,现在就让我们看看那种情况中的生命周期,并看看为什么这条规则意味着我们经常不需要在方法签名中标注生命周期。