生命周期基础

编译器通过生命周期来确保所有的借用都是合法的,典型的,一个变量在创建时生命周期随之开始,销毁时生命周期也随之结束。

生命周期的范围

  1. 🌟
1. /* 为 `i` 和 `borrow2` 标注合适的生命周期范围 */

3. // `i` 拥有最长的生命周期,因为它的作用域完整的包含了 `borrow1` 和 `borrow2` 。
4. // 而 `borrow1` 和 `borrow2` 的生命周期并无关联,因为它们的作用域没有重叠
5. fn main() {
6. let i = 3;
7. {
8. let borrow1 = &i; // `borrow1` 生命周期开始. ──┐
9. //                                                │
10. println!("borrow1: {}", borrow1); //              │
11. } // `borrow1` 生命周期结束. ──────────────────────────────────┘
12. {
13. let borrow2 = &i;

15. println!("borrow2: {}", borrow2);
16. }
17. }
  1. 🌟🌟

示例

1. #![allow(unused)]
2. fn main() {
3. {
4. let x = 5;            // ----------+-- 'b
5. //           |
6. let r = &x;           // --+-- 'a  |
7. //   |       |
8. println!("r: {}", r); //   |       |
9. // --+       |
10. }                         // ----------+
11. }
1. /* 像上面的示例一样,为 `r` 和 `x` 标注生命周期,然后从生命周期的角度. */

3. fn main() {
4. {
5. let r;                // ---------+-- 'a
6. //          |
7. {                     //          |
8. let x = 5;        // -+-- 'b  |
9. r = &x;           //  |       |
10. }                     // -+       |
11. //          |
12. println!("r: {}", r); //          |
13. }                         // ---------+
14. }

生命周期标注

Rust 的借用检查器使用显式的生命周期标注来确定一个引用的合法范围。但是对于用户来说,我们在大多数场景下,都无需手动去标注生命周期,原因是编译器会在某些情况下自动应用生命周期消除规则。

在了解编译器使用哪些规则帮我们消除生命周期之前,首先还是需要知道该如何手动标记生命周期。

函数

大家先忽略生命周期消除规则,让我们看看,函数签名中的生命周期有哪些限制:

  • 需要为每个引用标注上合适的生命周期
  • 返回值中的引用,它的生命周期要么跟某个引用参数相同,要么是 'static

示例

1. // 引用参数中的生命周期 'a 至少要跟函数活得一样久
2. fn print_one<'a>(x: &'a i32) {
3. println!("`print_one`: x is {}", x);
4. }

6. // 可变引用依然需要标注生命周期
7. fn add_one<'a>(x: &'a mut i32) {
8. *x += 1;
9. }

11. // 下面代码中,每个参数都拥有自己独立的生命周期,事实上,这个例子足够简单,因此它们应该被标记上相同的生命周期 `'a`,但是对于复杂的例子而言,独立的生命周期标注是可能存在的
12. fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) {
13. println!("`print_multi`: x is {}, y is {}", x, y);
14. }

16. // 返回一个通过参数传入的引用是很常见的,但是这种情况下需要标注上正确的生命周期
17. fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x }

19. fn main() {
20. let x = 7;
21. let y = 9;

23. print_one(&x);
24. print_multi(&x, &y);

26. let z = pass_x(&x, &y);
27. print_one(z);

29. let mut t = 3;
30. add_one(&mut t);
31. print_one(&t);
32. }
  1. 🌟
1. /* 添加合适的生命周期标注,让下面的代码工作 */
2. fn longest(x: &str, y: &str) -> &str {
3. if x.len() > y.len() {
4. x
5. } else {
6. y
7. }
8. }

10. fn main() {}
  1. 🌟🌟🌟
1. /* 使用三种方法修复下面的错误  */
2. fn invalid_output<'a>() -> &'a String {
3. &String::from("foo")
4. }

6. fn main() {
7. }
  1. 🌟🌟
1. // `print_refs` 有两个引用参数,它们的生命周期 `'a` 和 `'b` 至少得跟函数活得一样久
2. fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
3. println!("x is {} and y is {}", x, y);
4. }

6. /* 让下面的代码工作 */
7. fn failed_borrow<'a>() {
8. let _x = 12;

10. // ERROR: `_x` 活得不够久does not live long enough
11. let y: &'a i32 = &_x;

13. // 在函数内使用 `'a` 将会报错,原因是 `&_x` 的生命周期显然比 `'a` 要小
14. // 你不能将一个小的生命周期强转成大的
15. }

17. fn main() {
18. let (four, nine) = (4, 9);

20. print_refs(&four, &nine);
21. // 这里,four 和 nice 的生命周期必须要比函数 print_refs 长

23. failed_borrow();
24. // `failed_borrow`  没有传入任何引用去限制生命周期 `'a`,因此,此时的 `'a` 生命周期是没有任何限制的,它默认是 `'static`
25. }

Structs

  1. 🌟
1. /* 增加合适的生命周期标注,令代码正常工作 */

3. // `i32` 的引用必须比 `Borrowed` 活得更久
4. #[derive(Debug)]
5. struct Borrowed(&i32);

7. // 类似的,下面两个引用也必须比结构体 `NamedBorrowed` 活得更久
8. #[derive(Debug)]
9. struct NamedBorrowed {
10. x: &i32,
11. y: &i32,
12. }

14. #[derive(Debug)]
15. enum Either {
16. Num(i32),
17. Ref(&i32),
18. }

20. fn main() {
21. let x = 18;
22. let y = 15;

24. let single = Borrowed(&x);
25. let double = NamedBorrowed { x: &x, y: &y };
26. let reference = Either::Ref(&x);
27. let number    = Either::Num(y);

29. println!("x is borrowed in {:?}", single);
30. println!("x and y are borrowed in {:?}", double);
31. println!("x is borrowed in {:?}", reference);
32. println!("y is *not* borrowed in {:?}", number);
33. }
  1. 🌟🌟
1. /* 让代码工作 */

3. #[derive(Debug)]
4. struct NoCopyType {}

6. #[derive(Debug)]
7. struct Example<'a, 'b> {
8. a: &'a u32,
9. b: &'b NoCopyType
10. }

12. fn main()
13. {
14. let var_a = 35;
15. let example: Example;

17. {
18. let var_b = NoCopyType {};

20. /* 修复错误 */
21. example = Example { a: &var_a, b: &var_b };
22. }

24. println!("(Success!) {:?}", example);
25. }
  1. 🌟🌟
2. #[derive(Debug)]
3. struct NoCopyType {}

5. #[derive(Debug)]
6. #[allow(dead_code)]
7. struct Example<'a, 'b> {
8. a: &'a u32,
9. b: &'b NoCopyType
10. }

12. /* 修复函数的签名 */
13. fn fix_me(foo: &Example) -> &NoCopyType
14. { foo.b }

16. fn main()
17. {
18. let no_copy = NoCopyType {};
19. let example = Example { a: &1, b: &no_copy };
20. fix_me(&example);
21. println!("Success!")
22. }

方法

方法的生命周期标注跟函数类似。

示例

1. struct Owner(i32);

3. impl Owner {
4. fn add_one<'a>(&'a mut self) { self.0 += 1; }
5. fn print<'a>(&'a self) {
6. println!("`print`: {}", self.0);
7. }
8. }

10. fn main() {
11. let mut owner = Owner(18);

13. owner.add_one();
14. owner.print();
15. }
  1. 🌟🌟
1. /* 添加合适的生命周期让下面代码工作 */
2. struct ImportantExcerpt {
3. part: &str,
4. }

6. impl ImportantExcerpt {
7. fn level(&'a self) -> i32 {
8. 3
9. }
10. }

12. fn main() {}

生命周期消除( Elision )

有一些生命周期的标注方式很常见,因此编译器提供了一些规则,可以让我们在一些场景下无需去标注生命周期,既节省了敲击键盘的繁琐,又能提升可读性。

这种规则被称为生命周期消除规则( Elision ),该规则之所以存在,仅仅是因为这些场景太通用了,为了方便用户而已。事实上对于借用检查器而言,该有的生命周期一个都不能少,只不过对于用户而言,可以省去一些。

  1. 🌟🌟
1. /* 移除所有可以消除的生命周期标注 */

3. fn nput<'a>(x: &'a i32) {
4. println!("`annotated_input`: {}", x);
5. }

7. fn pass<'a>(x: &'a i32) -> &'a i32 { x }

9. fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
10. x
11. }

13. struct Owner(i32);

15. impl Owner {
16. fn add_one<'a>(&'a mut self) { self.0 += 1; }
17. fn print<'a>(&'a self) {
18. println!("`print`: {}", self.0);
19. }
20. }

22. struct Person<'a> {
23. age: u8,
24. name: &'a str,
25. }

27. enum Either<'a> {
28. Num(i32),
29. Ref(&'a i32),
30. }

32. fn main() {}

你可以在这里找到答案(在 solutions 路径下)