生命周期基础
编译器通过生命周期来确保所有的借用都是合法的,典型的,一个变量在创建时生命周期随之开始,销毁时生命周期也随之结束。
生命周期的范围
- 🌟
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. #![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. /* 添加合适的生命周期标注,让下面的代码工作 */
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. /* 使用三种方法修复下面的错误 */
2. fn invalid_output<'a>() -> &'a String {
3. &String::from("foo")
4. }
6. fn main() {
7. }
- 🌟🌟
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. /* 增加合适的生命周期标注,令代码正常工作 */
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. /* 让代码工作 */
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. }
- 🌟🌟
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. /* 添加合适的生命周期让下面代码工作 */
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. /* 移除所有可以消除的生命周期标注 */
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 路径下)
