Merge pull request #241 from JesseAtSZ/patch-12

Update basic.md
pull/244/head
Sunface 3 years ago committed by GitHub
commit 65a3bd181f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,13 +1,13 @@
# 认识生命周期
生命周期,简而言之就是引用的有效作用域。在大多数时候,我们无需手动的声明生命周期,因为编译器可以自动进行推导,用类型来类比下:
- 编译器大部分时候可以自动推导类型 <-> 编译器大多数时候也可以自动推导生命周期
- 就像编译器大部分时候可以自动推导类型 <-> 一样,编译器大多数时候也可以自动推导生命周期
- 在多种类型存在时,编译器往往要求我们手动标明类型 <-> 当多个生命周期存在,且编译器无法推导出某个引用的生命周期时,就需要我们手动标明生命周期
Rust生命周期之所以难是因为这个概念对于我们来说是全新没有其它编程语言的经验可以借鉴。当你觉得难的时候不用过于担心这个难对于所有人都是平等的多点付出就能早点解决此拦路虎同时本书也会尽力帮助大家减少学习难度(生命周期很可能是Rust中最难的部分).
Rust 生命周期之所以难,是因为这个概念对于我们来说是全新,没有其它编程语言的经验可以借鉴。当你觉得难的时候,不用过于担心,这个难对于所有人都是平等的,多点付出就能早点解决此拦路虎,同时本书也会尽力帮助大家减少学习难度(生命周期很可能是Rust中最难的部分)
## 悬垂指针和生命周期
生命周期的主要目标是避免悬垂引用,它会导致程序引用了本不该引用的数据:
生命周期的主要作用是避免悬垂引用,它会导致程序引用了本不该引用的数据:
```rust
{
let r;
@ -21,11 +21,11 @@ Rust生命周期之所以难是因为这个概念对于我们来说是全新
}
```
代码有几点值得注意:
这段代码有几点值得注意:
- `let r;` 的声明方式貌似存在使用 `null` 的风险,实际上,当我们不初始化它就使用时,编译器会给予报错
- `r` 引用了内部花括号中的 `x` 变量,但是 `x` 会在内部花括号 `}` 处被释放,因此回到外部花括号后,`r` 会引用一个无效的 `x`
此处`r`就是一个悬垂指针,它引用了提前被释放的变量`x`,可以预料到,该代码会报错:
此处 `r` 就是一个悬垂指针,它引用了提前被释放的变量 `x`,可以预料到,这段代码会报错:
```console
error[E0597]: `x` does not live long enough // x活得不够久
--> src/main.rs:7:17
@ -39,10 +39,10 @@ error[E0597]: `x` does not live long enough // x活得不够久
| - borrow later used here // 对x的借用在此处被使用
```
在这里`r`拥有更大的作用域,或者说**活得更久**。如Rust不阻止该垂悬引用的发生那么当`x`被释放后,`r`所引用的值就不再是合法的,会导致我们程序发生异常行为,且该异常行为有时候会很难被发现。
在这里 `r` 拥有更大的作用域,或者说**活得更久**。如Rust 不阻止该垂悬引用的发生,那么当 `x` 被释放后,`r` 所引用的值就不再是合法的,会导致我们程序发生异常行为,且该异常行为有时候会很难被发现。
## 借用检查
为了保证Rust的所有权和借用的正确性Rust使用了一个借用检查器(Borrow checker),来检查我们程序的借用正确性:
为了保证 Rust 的所有权和借用的正确性Rust 使用了一个借用检查器(Borrow checker),来检查我们程序的借用正确性
```rust
{
let r; // ---------+-- 'a
@ -58,9 +58,9 @@ error[E0597]: `x` does not live long enough // x活得不够久
这段代码和之前的一模一样,唯一的区别在于增加了对变量生命周期的注释。这里,`r` 变量被赋予了生命周期 `'a``x` 被赋予了生命周期 `'b`,从图示上可以明显看出生命周期 `'b``'a` 小很多。
在编译期Rust会比较两个变量的生命周期结果发现`r`明明拥有生命周期`'a`,但是却引用了一个小得多的生命周期`'b`, 在这种情况下,编译器会认为我们的程序存在风险,因此拒绝运行。
在编译期Rust 会比较两个变量的生命周期,结果发现 `r` 明明拥有生命周期 `'a`,但是却引用了一个小得多的生命周期 `'b`在这种情况下,编译器会认为我们的程序存在风险,因此拒绝运行。
如果想要编译通过,也很简单,只要`'b`比`'a`大就好。总之,`x`变量只要比`r`活得久,那么`r`就能随意引用`x`且不会存在危险:
如果想要编译通过,也很简单,只要 `'b``'a` 大就好。总之,`x` 变量只要比 `r` 活得久,那么 `r` 就能随意引用 `x` 且不会存在危险:
```rust
{
let x = 5; // ----------+-- 'b
@ -74,7 +74,7 @@ error[E0597]: `x` does not live long enough // x活得不够久
根据之前的结论,我们重新实现了代码,现在 `x` 的生命周期 `'b` 大于 `r` 的生命周期 `'a`,因此 `r``x` 的引用是安全的。
通过之前的内容我们了解了何为生命周期也了解了Rust如何利用生命周期来确保引用是合法的, 下面来看看函数中的生命周期。
通过之前的内容,我们了解了何为生命周期,也了解了 Rust 如何利用生命周期来确保引用是合法的下面来看看函数中的生命周期。
## 函数中的生命周期
先来考虑一个例子 - 返回两个字符串切片中较长的那个,该函数的参数是两个字符串切片,返回值也是字符串切片:
@ -88,7 +88,6 @@ fn main() {
}
```
这里值得注意的是,`longest`函数接收两个`&str`类型的字符串切片,因为我们并不想该函数把两个字符串的最珍贵的东西夺走:
```rust
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
@ -99,7 +98,7 @@ fn longest(x: &str, y: &str) -> &str {
}
```
这段`longest`实现,非常标准优美,就连多余的`return`和分号都没有,可是现实总是给我们重重一击:
这段 `longest` 实现,非常标准优美,就连多余的 `return` 和分号都没有,可是现实总是给我们重重一击
```console
error[E0106]: missing lifetime specifier
--> src/main.rs:9:33
@ -109,20 +108,20 @@ error[E0106]: missing lifetime specifier
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is
borrowed from `x` or `y`
= 帮助: 该函数的返回值是一个引用类型但是函数签名无法说明该引用是借用自x还是y
= 帮助: 该函数的返回值是一个引用类型,但是函数签名无法说明,该引用是借用自 `x` 还是 `y`
help: consider introducing a named lifetime parameter // 考虑引入一个生命周期
|
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
| ^^^^ ^^^^^^^ ^^^^^^^ ^^^
```
这真是一个复杂的提示那感觉就好像是生命周期去非诚勿扰相亲结果在初印象环节就23盏灯全灭。等等先别急如果你仔细阅读就会发现其实主要是编译器无法知道该函数的返回值到底引用`x`还是`y`,因为编译器需要知道这些,来确保函数调用后的引用生命周期分析。
这真是一个复杂的提示那感觉就好像是生命周期去非诚勿扰相亲结果在初印象环节就23盏灯全灭。等等先别急如果你仔细阅读就会发现其实主要是编译器无法知道该函数的返回值到底引用 `x` 还是 `y` **因为编译器需要知道这些,来确保函数调用后的引用生命周期分析**
不过说来尴尬,就这个函数而言,我们也不知道返回值到底引用哪个,因为一个分支返回 `x`,另一个分支返回 `y`...这可咋办?先来分析下。
我们在定义该函数时,首先无法知道传递给函数的具体值,因此到底是 `if` 还是 `else` 被执行,无从得知。其次,传入引用的具体生命周期也无法知道,因此也不能像之前的例子那样通过分析生命周期来确定引用是否有效。同时,编译器的借用检查也无法推导出返回值的生命周期,因为它不知道 `x``y` 的生命周期跟返回值的生命周期之间的关系是怎样的(说实话,人都搞不清,何况编译器这个大聪明)。
因此,这时就回到了文章开头说的内容:在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析。
因此,这时就回到了文章开头说的内容:在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析。
## 生命周期标注语法
> 生命周期标注并不会改变任何引用的实际作用域 - 鲁迅
@ -131,9 +130,9 @@ help: consider introducing a named lifetime parameter // 考虑引入一个生
在很多时候编译器是很聪明的,但是总有些时候,它会化身大聪明,自以为什么都很懂,然后去拒绝我们代码的执行,此时,就需要我们通过生命周期标注来告诉这个大聪明:别自作聪明了,听我的就好。
例如一个变量,只能活一个花括号,那么就算你给它标注一个活全局的生命周期,它还是会在前面的花括号结束处被释放掉,并不会真的活全局.
例如一个变量,只能活一个花括号,那么就算你给它标注一个活全局的生命周期,它还是会在前面的花括号结束处被释放掉,并不会真的全局存活。
生命周期的语法也颇为与众不同,以`'`开头,名称往往是一个单独的小写字母, 大多数人都用`'a`来作为生命周期的名称。 如果是引用类型的参数,那么生命周期会位于引用符号`&`之后,并用一个空格来将生命周期和引用参数分隔开:
生命周期的语法也颇为与众不同,以 `'` 开头,名称往往是一个单独的小写字母,大多数人都用 `'a` 来作为生命周期的名称。 如果是引用类型的参数,那么生命周期会位于引用符号 `&` 之后,并用一个空格来将生命周期和引用参数分隔开:
```rust
&i32 // 一个引用
&'a i32 // 具有显式生命周期的引用
@ -146,7 +145,7 @@ fn useless<'a>(first: &'a i32, second: &'a i32) {}
```
#### 函数签名中的生命周期标注
继续之前的`longest`函数,从两个字符串切片中返回较长的那个:
继续之前的 `longest` 函数,从两个字符串切片中返回较长的那个
```rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
@ -163,11 +162,11 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
该函数签名表明对于某些生命周期 `'a`,函数的两个参数都至少跟 `'a` 活得一样久,同时函数的返回引用也至少跟 `'a` 活得一样久。实际上,这意味着返回值的生命周期与参数生命周期中的较小值一致:虽然两个参数的生命周期都是标注了 `'a`,但是实际上这两个参数的真实生命周期可能是不一样的(生命周期 `'a` 不代表生命周期等于 `'a`,而是大于等于 `'a`)。
回忆下“鲁迅”说的话,再参考上面的内容,可以得出:在通过函数签名指定生命周期参数时,我们并没有改变传入引用或者返回引用的真实生命周期,而是告诉编译器当不满足此约束条件时,就拒绝编译通过。
回忆下“鲁迅”说的话,再参考上面的内容,可以得出:**在通过函数签名指定生命周期参数时,我们并没有改变传入引用或者返回引用的真实生命周期,而是告诉编译器当不满足此约束条件时,就拒绝编译通过**
因此 `longest` 函数并不知道 `x``y` 具体会活多久,只要知道它们的作用域至少能持续 `'a` 这么长就行。
当把具体的引用传给`longest`时,那生命周期`'a`的大小就是`x`和`y`的作用域的重合部分。换句话说,`'a`的大小将等于`x`和`y`中较小的那个。由于返回值的生命周期也被标记为`'a`,因此返回值的生命周期也是`x`和`y`中作用域较小的那个。
当把具体的引用传给 `longest` 时,那生命周期 `'a` 的大小就是 `x``y` 的作用域的重合部分,换句话说,`'a` 的大小将等于 `x``y` 中较小的那个。由于返回值的生命周期也被标记为 `'a`,因此返回值的生命周期也是 `x` `y` 中作用域较小的那个。
说实话,这段文字我写的都快崩溃了,不知道你们读起来如何,实在***太绕了。。那就干脆用一个例子来解释吧:
```rust
@ -201,7 +200,7 @@ fn main() {
}
```
Bang, 错误冒头了:
Bang错误冒头了:
```console
error[E0597]: `string2` does not live long enough
--> src/main.rs:6:44
@ -214,9 +213,9 @@ error[E0597]: `string2` does not live long enough
| ------ borrow later used here
```
在上述代码中,`result`必须要活到`println!`处,因为`result`的生命周期是`'a`,因此`'a`必须持续到`println!`.
在上述代码中,`result` 必须要活到 `println!`处,因为 `result` 的生命周期是 `'a`,因此 `'a` 必须持续到 `println!`
`longest`函数中,`string2`的生命周期也是`'a`,由此说明`string2`也必须活到`println!`处, 可是`string2`在代码中实际上只能活到内部语句块的花括号处`}`,小于它应该具备的生命周期`'a`,因此编译出错。
`longest` 函数中,`string2` 的生命周期也是 `'a`,由此说明 `string2` 也必须活到 `println!` 处,可是 `string2` 在代码中实际上只能活到内部语句块的花括号处 `}`,小于它应该具备的生命周期 `'a`,因此编译出错。
作为人类,我们可以很清晰的看出 `result` 实际上引用了 `string1`,因为 `string1` 的长度明显要比 `string2` 长,既然如此,编译器不该如此矫情才对,它应该能认识到 `result` 没有引用 `string2`,让我们这段代码通过。只能说,作为尊贵的人类,编译器的发明者,你高估了这个工具的能力,它真的做不到!而且 Rust 编译器在调教上是非常保守的:当可能出错也可能不出错时,它会选择前者,抛出编译错误。
@ -246,7 +245,7 @@ fn longest<'a>(x: &str, y: &str) -> &'a str {
}
```
上面的函数的返回值就和参数`xy`没有任何关系,而是引用了函数体内创建的字符串,那么很显然,该函数会报错:
上面的函数的返回值就和参数 `x``y` 没有任何关系,而是引用了函数体内创建的字符串,那么很显然,该函数会报错:
```console
error[E0515]: cannot return value referencing local variable `result` // 返回值result引用了本地的变量
--> src/main.rs:11:5
@ -291,9 +290,9 @@ fn main() {
}
```
`ImportantExcerpt`结构体中有一个引用类型的字段`part`,因此需要为它标注上生命周期。结构体的生命周期标注语法跟泛型参数语法很像,需要对生命周期参数进行声明`<'a>`. 该生命周期标注说明, **结构体`ImportantExcerpt`所引用的字符串`str`必须比该结构体活得更久**。
`ImportantExcerpt` 结构体中有一个引用类型的字段 `part`,因此需要为它标注上生命周期。结构体的生命周期标注语法跟泛型参数语法很像,需要对生命周期参数进行声明 `<'a>`。该生命周期标注说明,**结构体 `ImportantExcerpt` 所引用的字符串 `str` 必须比该结构体活得更久**。
从`main`函数实现来看,`ImportantExcerpt`的生命周期从第4行开始到`main`函数末尾结束,而该结构体引用的字符串从第一行开始,也是到`main`函数末尾结束,可以得出结论**结构体引用的字符串活得比结构体久**,这符合了编译器对生命周期的要求,因此编译通过.
`main` 函数实现来看,`ImportantExcerpt` 的生命周期从第4行开始 `main` 函数末尾结束,而该结构体引用的字符串从第一行开始,也是到 `main` 函数末尾结束,可以得出结论**结构体引用的字符串活得比结构体久**,这符合了编译器对生命周期的要求,因此编译通过
与之相反,下面的代码就无法通过编译:
```rust
@ -330,7 +329,7 @@ error[E0597]: `novel` does not live long enough
```
## 生命周期消除
实际上,对于编译器来说,每一个引用类型都有一个生命周期,那么为什么我们在使用过程中,很多时候无需标注生命周期?例如:
实际上,对于编译器来说,每一个引用类型都有一个生命周期,那么为什么我们在使用过程中,很多时候无需标注生命周期?例如
```rust
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
@ -351,31 +350,31 @@ fn first_word(s: &str) -> &str {
- 从参数获取
- 从函数体内部新创建的变量获取
如果是后者,就会出现悬垂引用,最终被编译器拒绝,因此只剩一种情况: 返回值的引用是获取自参数,这就意味着参数和返回值的生命周期是一样的。道理很简单,我们能看出来,编译器自然也能看出来,因此,就算我们不标注生命周期,也不会产生歧义。
如果是后者,就会出现悬垂引用,最终被编译器拒绝,因此只剩一种情况返回值的引用是获取自参数,这就意味着参数和返回值的生命周期是一样的。道理很简单,我们能看出来,编译器自然也能看出来,因此,就算我们不标注生命周期,也不会产生歧义。
实际上在Rust 1.0版本之前这种代码果断不给通过因为Rust要求必须显式的为所有引用标注生命周期:
实际上,在 Rust 1.0 版本之前,这种代码果断不给通过,因为 Rust 要求必须显式的为所有引用标注生命周期
```rust
fn first_word<'a>(s: &'a str) -> &'a str {
```
在写了大量的类似代码后Rust社区抱怨声四起包括开发者自己都忍不了了最终揭锅而起这才有了我们今日的幸福。
生命周期消除的规则不是一蹴而就,而是随着`总结-改善`流程的周而复始一步一步走到今天这也意味着该规则以后可能也会进一步增加我们需要手动标注生命周期的时候也会越来越少hooray!
生命周期消除的规则不是一蹴而就,而是随着 `总结-改善` 流程的周而复始一步一步走到今天这也意味着该规则以后可能也会进一步增加我们需要手动标注生命周期的时候也会越来越少hooray!
在开始之前有几点需要注意:
- 消除规则不是万能的,若编译器不能确定某件事是正确时,会直接判为不正确,那么你还是需要手动标注生命周期
- **函数或者方法中,参数的生命周期被称为 `输入生命周期`,返回值的生命周期被称为 `输出生命周期`**
#### 三条消除规则
编译器使用三条消除规则来确定哪些场景不需要显式去标注生命周期。其中第一条规则应用在输入生命周期上,第二、三条应用在输出生命周期上。若编译器发现三条规则都不适用时,就会报错,提示你需要手动标注生命周期。
编译器使用三条消除规则来确定哪些场景不需要显式去标注生命周期。其中第一条规则应用在输入生命周期上,第二、三条应用在输出生命周期上。若编译器发现三条规则都不适用时,就会报错,提示你需要手动标注生命周期。
1. **每一个引用参数都会获得独自的生命周期**
例如一个引用参数的函数就有一个生命周期标注: `fn foo<'a>(x: &'a i32)`, 两个引用参数的有两个生命周期标注:`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`, 依此类推.
例如一个引用参数的函数就有一个生命周期标注: `fn foo<'a>(x: &'a i32)`两个引用参数的有两个生命周期标注:`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`, 依此类推
2. **若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期**,也就是所有返回值的生命周期都等于该输入生命周期
2. **若只有一个输入生命周期(函数参数中只有一个引用类型)那么该生命周期会被赋给所有的输出生命周期**,也就是所有返回值的生命周期都等于该输入生命周期
例如函数`fn foo(x: &i32) -> &i32`, `x`参数的生命周期会被自动赋给返回值`&i32`,因此该函数等同于`fn foo<'a>(x: &'a i32) -> &'a i32`
例如函数 `fn foo(x: &i32) -> &i32``x` 参数的生命周期会被自动赋给返回值 `&i32`,因此该函数等同于 `fn foo<'a>(x: &'a i32) -> &'a i32`
3. **若存在多个输入生命周期,且其中一个是 `&self``&mut self`,则 `&self` 的生命周期被赋给所有的输出生命周期**
@ -391,17 +390,17 @@ fn first_word<'a>(s: &'a str) -> &'a str {
fn first_word(s: &str) -> &str { // 实际项目中的手写代码
```
首先,我们手写的代码如上所示时,编译器会先应用第一条规则,为每个参数标注一个生命周期:
首先,我们手写的代码如上所示时,编译器会先应用第一条规则,为每个参数标注一个生命周期
```rust
fn first_word<'a>(s: &'a str) -> &str { // 编译器自动为参数添加生命周期
```
此时,第二条规则就可以进行应用,因为函数只有一个输入生命周期,因此该生命周期会被赋予所有的输出生命周期:
此时,第二条规则就可以进行应用,因为函数只有一个输入生命周期,因此该生命周期会被赋予所有的输出生命周期
```rust
fn first_word<'a>(s: &'a str) -> &'a str { // 编译器自动为返回值添加生命周期
```
此时,编译器为函数签名中的所有引用都自动添加了具体的生命周期,因此编译通过,且用户无需手动去标注生命周期,只要按照`fn first_word(s: &str) -> &str { `的形式写代码即可.
此时,编译器为函数签名中的所有引用都自动添加了具体的生命周期,因此编译通过,且用户无需手动去标注生命周期,只要按照 `fn first_word(s: &str) -> &str { ` 的形式写代码即可。
**例子2**
再来看一个例子:
@ -414,7 +413,7 @@ fn longest(x: &str, y: &str) -> &str { // 实际项目中的手写代码
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
```
但是此时,第二条规则却无法被使用,因为输入生命周期有两个,第三条规则也不符合,因为它是函数,不是方法,因此没有`&self`参数。在套用所有规则后,编译器依然无法为返回值标注合适的生命周期,因此,编译器就会报错,提示我们需要手动标注生命周期:
但是此时,第二条规则却无法被使用,因为输入生命周期有两个,第三条规则也不符合,因为它是函数,不是方法,因此没有 `&self` 参数。在套用所有规则后,编译器依然无法为返回值标注合适的生命周期,因此,编译器就会报错,提示我们需要手动标注生命周期
```console
error[E0106]: missing lifetime specifier
--> src/main.rs:1:47
@ -434,7 +433,7 @@ help: consider using one of the available lifetimes here
| +++++++++
```
不得不说,Rust编译器真的很强大,还贴心的给我们提示了该如何修改,虽然。。。好像。。。。它的提示貌似不太准确。这里我们更希望参数和返回值都是`'a`生命周期.
不得不说Rust 编译器真的很强大,还贴心的给我们提示了该如何修改,虽然。。。好像。。。。它的提示貌似不太准确。这里我们更希望参数和返回值都是 `'a` 命周期。
## 方法中的生命周期
先来回忆下泛型的语法:
@ -451,7 +450,7 @@ impl<T> Point<T> {
}
```
实际上,为具有生命周期的结构体实现方法时,我们使用的语法跟泛型参数语法很相似:
实际上,为具有生命周期的结构体实现方法时,我们使用的语法跟泛型参数语法很相似
```rust
struct ImportantExcerpt<'a> {
part: &'a str,
@ -465,10 +464,10 @@ impl<'a> ImportantExcerpt<'a> {
```
其中有几点需要注意的:
- `impl`中必须使用结构体的完整名称,包括`<'a>`,因为*生命周期标注也是结构体类型的一部分*!
- `impl` 中必须使用结构体的完整名称,包括 `<'a>`,因为*生命周期标注也是结构体类型的一部分*
- 方法签名中,往往不需要标注生命周期,得益于生命周期消除的第一和第三规则
下面的例子展示了第三规则应用的场景:
下面的例子展示了第三规则应用的场景
```rust
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part(&self, announcement: &str) -> &str {
@ -488,9 +487,9 @@ impl<'a> ImportantExcerpt<'a> {
}
```
需要注意的是,编译器不知道`announcement`的生命周期到底多长,因此它无法简单的给予它生命周期`'a`,而是重新声明了一个全新的生命周期`'b`.
需要注意的是,编译器不知道 `announcement` 的生命周期到底多长,因此它无法简单的给予它生命周期 `'a`,而是重新声明了一个全新的生命周期 `'b`
接着,编译器应用第三规则,将`&self`的生命周期赋给返回值`&str`:
接着,编译器应用第三规则,将 `&self` 的生命周期赋给返回值 `&str`
```rust
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'a str {
@ -502,7 +501,7 @@ impl<'a> ImportantExcerpt<'a> {
Bingo最开始的代码尽管我们没有给方法标注生命周期但是在第一和第三规则的配合下编译器依然完美的为我们亮起了绿灯。
在结束这块儿内容之前,再来做一个有趣的修改,将方法返回的生命周期改为`'b`:
在结束这块儿内容之前,再来做一个有趣的修改,将方法返回的生命周期改为`'b`
```rust
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'b str {
@ -512,9 +511,9 @@ impl<'a> ImportantExcerpt<'a> {
}
```
此时,编译器会报错,因为编译器无法知道`'a`和`'b`的关系。 `&self`生命周期是`'a`,那么`self.part`的生命周期也是`'a`,但是好巧不巧的是,我们手动为返回值`self.part`标注了生命周期`'b`,因此编译器需要知道`'a`和`'b`的关系。
此时,编译器会报错,因为编译器无法知道 `'a``'b` 的关系。 `&self` 生命周期是 `'a`,那么 `self.part` 的生命周期也是 `'a`,但是好巧不巧的是,我们手动为返回值 `self.part` 标注了生命周期 `'b`,因此编译器需要知道 `'a` `'b` 的关系。
有一点很容易推理出来:由于`&'a self`是被引用的一方,因此引用它的`&'b str`必须要活得比它短, 否则会出现悬垂引用。因此说明生命周期`'b`必须要比`'a`小,只要满足了这一点,编译器就不会再报错:
有一点很容易推理出来:由于 `&'a self` 是被引用的一方,因此引用它的 `&'b str` 必须要活得比它短,否则会出现悬垂引用。因此说明生命周期 `'b` 必须要比 `'a` 小,只要满足了这一点,编译器就不会再报错
```rust
impl<'a: 'b, 'b> ImportantExcerpt<'a> {
fn announce_and_return_part(&'a self, announcement: &'b str) -> &'b str {
@ -533,9 +532,9 @@ Bang一个复杂的玩意儿被甩到了你面前就问怕不怕
总之,实现方法比想象中简单:加一个约束,就能暗示编译器,尽管引用吧,反正我想引用的内容比我活得久,爱咋咋地,我怎么都不会引用到无效的内容!
## 静态生命周期
在Rust中有一个非常特殊的生命周期那就是`'static`,该生命周期意味着被它标注的引用在编译器看来可以和整个程序活得一样久(强烈建议再看看这句[名言](#生命周期标注语法)!)。
在Rust中有一个非常特殊的生命周期那就是 `'static`,该生命周期意味着被它标注的引用**在编译器看来**可以和整个程序活得一样久(强烈建议再看看这句[名言](#生命周期标注语法))。
在之前我们学过字符串字面量提到过它是被硬编码进Rust的二进制文件中因此这些字符串变量全部具有`'static`的生命周期:
在之前我们学过字符串字面量,提到过它是被硬编码进 Rust 的二进制文件中,因此这些字符串变量全部具有 `'static` 的生命周期:
```rust
let s: &'static str = "我没啥优点,就是活得久,嘿嘿";
```
@ -546,7 +545,7 @@ let s: &'static str = "我没啥优点,就是活得久,嘿嘿";
因此,遇到因为生命周期导致的编译不通过问题,首先想的应该是:是否是我们试图创建一个悬垂引用,或者是试图匹配不一致的生命周期,而不是简单粗暴的用 `'static` 来解决问题。
但是,话说回来,存在即合理,有时候,`'static`确实可以帮助我们解决非常复杂的生命周期问题甚至是无法被手动解决的生命周期问题,那么此时就应该放心大胆的用,只要你确定:**你的所有引用的生命周期都是正确的,只是编译器太笨不懂罢了**.
但是,话说回来,存在即合理,有时候,`'static` 确实可以帮助我们解决非常复杂的生命周期问题甚至是无法被手动解决的生命周期问题,那么此时就应该放心大胆的用,只要你确定:**你的所有引用的生命周期都是正确的,只是编译器太笨不懂罢了**
总结下:
- 字符串字面量的生命周期都是 `'static`
@ -555,7 +554,7 @@ let s: &'static str = "我没啥优点,就是活得久,嘿嘿";
## 一个复杂例子: 泛型、特征约束
手指已经疲软无力,我好想停止,但是华丽的开场都要有与之匹配的谢幕,那我们就用一个稍微复杂点的例子来结束:
手指已经疲软无力,我好想停止,但是华丽的开场都要有与之匹配的谢幕,那我们就用一个稍微复杂点的例子来结束
```rust
use std::fmt::Display;

Loading…
Cancel
Save