Merge pull request #130 from 1132719438/main

Some fixes in lifetime chapter
pull/135/head
Sunface 3 years ago committed by GitHub
commit ad3e52df52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -22,7 +22,7 @@ fn main() {
} }
``` ```
上面的代码中,`foo.mutate_and_share()`虽然借用了`&mut self`,但是它最终返回的是一个`&self`,然后赋值给`loan`,因此理论上来说它最终是进行了不可变借用,同时`foo.share`也进行了不可变借用那么根据Rust的借用规则多个不可借用可以同时存在,因此该代码应该编译通过。 上面的代码中,`foo.mutate_and_share()`虽然借用了`&mut self`,但是它最终返回的是一个`&self`,然后赋值给`loan`,因此理论上来说它最终是进行了不可变借用,同时`foo.share`也进行了不可变借用那么根据Rust的借用规则多个不可借用可以同时存在,因此该代码应该编译通过。
事实上,运行代码后,你将看到一个错误: 事实上,运行代码后,你将看到一个错误:
```console ```console
@ -124,6 +124,7 @@ error[E0499]: cannot borrow `*map` as mutable more than once at a time
## 无界生命周期 ## 无界生命周期
不安全代码(`unsafe`)经常会凭空产生引用或生命周期, 这些生命周期被称为是 **无界(unbound)** 的。 不安全代码(`unsafe`)经常会凭空产生引用或生命周期, 这些生命周期被称为是 **无界(unbound)** 的。
无界生命周期往往是在解引用一个原生指针(裸指针raw pointer)时产生的,换句话说,它是凭空产生的,因为输入参数根本就没有这个生命周期: 无界生命周期往往是在解引用一个原生指针(裸指针raw pointer)时产生的,换句话说,它是凭空产生的,因为输入参数根本就没有这个生命周期:
@ -163,7 +164,7 @@ struct Ref<'a, T: 'a> {
} }
``` ```
因为结构体字段`r`引用了`T`,因此`r`的生命周期`'a`必须要比`T`的生命周期更(被引用者的生命周期必须要比引用长)。 因为结构体字段`r`引用了`T`,因此`r`的生命周期`'a`必须要比`T`的生命周期更(被引用者的生命周期必须要比引用长)。
在Rust 1.30版本之前该写法是必须的但是从1.31版本开始,编译器可以自动推导`T: 'a`类型的约束,因此我们只需这样写即可: 在Rust 1.30版本之前该写法是必须的但是从1.31版本开始,编译器可以自动推导`T: 'a`类型的约束,因此我们只需这样写即可:
```rust ```rust
@ -290,9 +291,6 @@ impl<'a> Reader for BufReader<'a> {
``` ```
如果你以前写的`impl`块长上面这样, 同时在`impl`内部的方法中,根本就没有用到`'a`,那就可以写成下面的代码形式。 如果你以前写的`impl`块长上面这样, 同时在`impl`内部的方法中,根本就没有用到`'a`,那就可以写成下面的代码形式。
歪个楼,有读者估计会发问:既然用不到`'a`,为何还要写出来?如果你仔细回忆下上一节的内容,里面有一句专门用粗体标注的文字:**生命周期参数也是类型的一部分**,因此`BufReader<'a>`是一个完整的类型,在实现它的时候,你不能把`'a`给丢了!
```rust ```rust
impl Reader for BufReader<'_> { impl Reader for BufReader<'_> {
// methods go here // methods go here
@ -301,6 +299,7 @@ impl Reader for BufReader<'_> {
`'_`生命周期表示`BufReader`有一个不使用的生命周期,我们可以忽略它,无需为它创建一个名称。 `'_`生命周期表示`BufReader`有一个不使用的生命周期,我们可以忽略它,无需为它创建一个名称。
歪个楼,有读者估计会发问:既然用不到`'a`,为何还要写出来?如果你仔细回忆下上一节的内容,里面有一句专门用粗体标注的文字:**生命周期参数也是类型的一部分**,因此`BufReader<'a>`是一个完整的类型,在实现它的时候,你不能把`'a`给丢了!
#### 生命周期约束消除 #### 生命周期约束消除
```rust ```rust
@ -384,7 +383,7 @@ error[E0502]: cannot borrow `list` as immutable because it is also borrowed as m
这段代码看上去并不复杂,实际上难度挺高的,首先在直觉上,`list.get_interface()`借用的可变引用,按理来说应该在这行代码结束后,就归还了,为何能持续到`use_list(&list)`后面呢? 这段代码看上去并不复杂,实际上难度挺高的,首先在直觉上,`list.get_interface()`借用的可变引用,按理来说应该在这行代码结束后,就归还了,为何能持续到`use_list(&list)`后面呢?
这是因为我们在`get_interface`方法中声明的`lifetime`有问题,该方法的参数的生周期是`'a`,而`List`的生命周期也是`'a`,说明该方法至少活得跟`List`一样久,再回到`main`函数中,`list`可以活到`main`函数的结束,因此`list.get_interface()`借用的可变引用也会活到`main`函数的结束,在此期间,自然无法再进行借用了。 这是因为我们在`get_interface`方法中声明的`lifetime`有问题,该方法的参数的生周期是`'a`,而`List`的生命周期也是`'a`,说明该方法至少活得跟`List`一样久,再回到`main`函数中,`list`可以活到`main`函数的结束,因此`list.get_interface()`借用的可变引用也会活到`main`函数的结束,在此期间,自然无法再进行借用了。
要解决这个问题,我们需要为`get_interface`方法的参数给予一个不同于`List<'a>`的生命周期`'b`,最终代码如下: 要解决这个问题,我们需要为`get_interface`方法的参数给予一个不同于`List<'a>`的生命周期`'b`,最终代码如下:
```rust ```rust
@ -427,8 +426,7 @@ fn main() {
println!("Interface should be dropped here and the borrow released"); println!("Interface should be dropped here and the borrow released");
// 下面的调用会失败,因为同时有不可变/可变借用 // 下面的调用可以通过因为Interface的生命周期不需要跟list一样长
// 但是Interface在之前调用完成后就应该被释放了
use_list(&list); use_list(&list);
} }

@ -1,7 +1,7 @@
# 认识生命周期 # 认识生命周期
生命周期,简而言之就是引用的有效作用域。在大多数时候,我们无需手动的声明生命周期,因为编译器可以自动进行推导,用类型来比下: 生命周期,简而言之就是引用的有效作用域。在大多数时候,我们无需手动的声明生命周期,因为编译器可以自动进行推导,用类型来比下:
- 编译器大部分时候可以自动推导类型 <-> 编译器大多数时候也可以自动推导生周期 - 编译器大部分时候可以自动推导类型 <-> 编译器大多数时候也可以自动推导生周期
- 在多种类型存在时,编译器往往要求我们手动标明类型 <-> 当多个生命周期存在,且编译器无法推导出某个引用的生命周期时,就需要我们手动标明生命周期 - 在多种类型存在时,编译器往往要求我们手动标明类型 <-> 当多个生命周期存在,且编译器无法推导出某个引用的生命周期时,就需要我们手动标明生命周期
Rust生命周期之所以难是因为这个概念对于我们来说是全新没有其它编程语言的经验可以借鉴。当你觉得难的时候不用过于担心这个难对于所有人都是平等的多点付出就能早点解决此拦路虎同时本书也会尽力帮助大家减少学习难度(生命周期很可能是Rust中最难的部分). Rust生命周期之所以难是因为这个概念对于我们来说是全新没有其它编程语言的经验可以借鉴。当你觉得难的时候不用过于担心这个难对于所有人都是平等的多点付出就能早点解决此拦路虎同时本书也会尽力帮助大家减少学习难度(生命周期很可能是Rust中最难的部分).
@ -120,7 +120,7 @@ help: consider introducing a named lifetime parameter // 考虑引入一个生
不过说来尴尬,就这个函数而言,我们也不知道返回值到底引用哪个,因为一个分支返回`x`,另一个分支返回`y`...这可咋办?先来分析下。 不过说来尴尬,就这个函数而言,我们也不知道返回值到底引用哪个,因为一个分支返回`x`,另一个分支返回`y`...这可咋办?先来分析下。
我们在定义该函数时,首先无法知道传递给函数的具体值,因此到底是`if`还是`else`被执行,无从得知。其次,传入引用的具体生周期也无法知道,因此也不能像之前的例子那样通过分析生命周期来确定引用是否有效。同时,编译器的借用检查也无法推导出返回值的生命周期,因为它不知道`x`和`y`的生命周期跟返回值的生命周期之间的关系是怎样的(说实话,人都搞不清,何况编译器这个大聪明)。 我们在定义该函数时,首先无法知道传递给函数的具体值,因此到底是`if`还是`else`被执行,无从得知。其次,传入引用的具体生周期也无法知道,因此也不能像之前的例子那样通过分析生命周期来确定引用是否有效。同时,编译器的借用检查也无法推导出返回值的生命周期,因为它不知道`x`和`y`的生命周期跟返回值的生命周期之间的关系是怎样的(说实话,人都搞不清,何况编译器这个大聪明)。
因此,这时就回到了文章开头说的内容:在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析。 因此,这时就回到了文章开头说的内容:在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析。
@ -161,7 +161,7 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
- 和泛型一样,使用生命周期参数,需要先声明`<'a>` - 和泛型一样,使用生命周期参数,需要先声明`<'a>`
- `x`、`y`和返回值至少活得和`'a`一样久(因为返回值要么是`x`,要么是`y`) - `x`、`y`和返回值至少活得和`'a`一样久(因为返回值要么是`x`,要么是`y`)
该函数签名表明对于某些生命周期`'a`,函数的两个参数都至少跟`'a`活得一样久,同时函数的返回引用也至少跟`'a`活得一样久。实际上,这意味着返回值的生命周期与参数生命周期中的较小值一致:虽然两个参数的生命周期都是标注了`'a`,但是实际上这两个参数的真实生命周期可能是不一样的(生周期`'a`不代表生命周期等于`'a`,而是大于等于`'a`)。 该函数签名表明对于某些生命周期`'a`,函数的两个参数都至少跟`'a`活得一样久,同时函数的返回引用也至少跟`'a`活得一样久。实际上,这意味着返回值的生命周期与参数生命周期中的较小值一致:虽然两个参数的生命周期都是标注了`'a`,但是实际上这两个参数的真实生命周期可能是不一样的(生周期`'a`不代表生命周期等于`'a`,而是大于等于`'a`)。
回忆下“鲁迅”说的话,再参考上面的内容,可以得出:在通过函数签名指定生命周期参数时,我们并没有改变传入引用或者返回引用的真实生命周期,而是告诉编译器当不满足此约束条件时,就拒绝编译通过。 回忆下“鲁迅”说的话,再参考上面的内容,可以得出:在通过函数签名指定生命周期参数时,我们并没有改变传入引用或者返回引用的真实生命周期,而是告诉编译器当不满足此约束条件时,就拒绝编译通过。
@ -554,7 +554,7 @@ let s: &'static str = "我没啥优点,就是活得久,嘿嘿";
- 实在遇到解决不了的生命周期标注问题,可以尝试`'static`,有时候它会给你奇迹 - 实在遇到解决不了的生命周期标注问题,可以尝试`'static`,有时候它会给你奇迹
## 一个复杂例子: 泛型、特征约束、特征约束 ## 一个复杂例子: 泛型、特征约束
手指已经疲软无力,我好想停止,但是华丽的开场都要有与之匹配的谢幕,那我们就用一个稍微复杂点的例子来结束: 手指已经疲软无力,我好想停止,但是华丽的开场都要有与之匹配的谢幕,那我们就用一个稍微复杂点的例子来结束:
```rust ```rust
use std::fmt::Display; use std::fmt::Display;

Loading…
Cancel
Save