生命周期

  • Rust 的每个引用都有自己的生命周期
  • 生命周期:引用保持有效的作用域
  • 大多数情况:生命周期是隐式的、可被推断的
  • 当引用的生命周期可能以不同的方式互相关联时:手动标注生命周期。

    生命周期 - 避免悬垂引用(dangling reference)

  • 生命周期的主要目标:避免悬垂引用(dangling reference) ```rust fn main() { {

    1. let r; // 变量声明,未初始化
    2. {
    3. let x = 5;
    4. r = &x;
    5. }
    6. println!("r: {}", r); // 使用 r 时,r 指向的 x 已经离开作用域了

    } }

// error[E0597]: x does not live long enough // —> src/main.rs:6:17 // | // 6 | r = &x; // | ^^ borrowed value does not live long enough // 7 | } // | - x dropped here while still borrowed // 8 | println!(“r: {}”, r); // | - borrow later used here // // error: aborting due to previous error

  1. <a name="mmIuz"></a>
  2. ## 借用检查器
  3. - Rust 编译器的借用检查器:比较作用域来判断所有的借用是否合法
  4. ```rust
  5. {
  6. let r; // ---------+-- 'a
  7. // |
  8. { // |
  9. let x = 5; // -+-- 'b |
  10. r = &x; // | |
  11. } // -+ |
  12. // |
  13. println!("r: {}", r); // |
  14. } // ---------+

被引用的生命周期(x),要比它的引用者(r)的生命周期短 解决的方式,就是让x的生命周期至少要不小于r的生命周期

  1. {
  2. let x = 5; // ----------+-- 'b
  3. // |
  4. let r = &x; // --+-- 'a |
  5. // | |
  6. println!("r: {}", r); // | |
  7. // --+ |
  8. } // ----------+

函数中的泛型生命周期

  1. fn main() {
  2. let string1 = String::from("abcd"); // String类型
  3. let string2 = "xyz"; // 字符串字面值类型,也就是字符串切片类型
  4. let result = longest(string1.as_str(), string2);
  5. println!("The longest string is {}", result);
  6. }
  7. fn longest(x: &str, y: &str) -> &str {
  8. if x.len() > y.len() {
  9. x
  10. } else {
  11. y
  12. }
  13. }
  14. // error[E0106]: missing lifetime specifier 缺少生命周期的标注
  15. // --> src/main.rs:10:33
  16. // |
  17. // 10 | fn longest(x: &str, y: &str) -> &str {
  18. // | ---- ---- ^ expected named lifetime parameter
  19. // | 缺少命名的生命周期参数
  20. // = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
  21. // 函数的返回类型包含了一个借用的值,但是函数的签名没有说明借用的值是来自x还是来自y
  22. // help: consider introducing a named lifetime parameter
  23. // 考虑引入一个命名的生命周期参数
  24. // |
  25. // 10 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  26. // | ^^^^ ^^^^^^^ ^^^^^^^ ^^^
  27. //
  28. // error: aborting due to previous error

longest函数的返回值,要么是x要么是y,但具体是哪个这个不一定,而x和y这两个传入的参数, 它们具体的生命周期也是不知道的,也没法比较作用域,从而判断返回值的引用是否一直有效的。 而借用检查器也是做不到的,原因就是返回类型的生命周期不知道是和x还是和y有关系。 函数签名里,体现不出来返回类型借用的值是来自x还是来自y。 所以与函数体里的逻辑没有关系,就是与函数签名有关。

加入泛型生命周期参数

  1. fn main() {
  2. let string1 = String::from("abcd"); // String类型
  3. let string2 = "xyz"; // 字符串字面值类型,也就是字符串切片类型
  4. let result = longest(string1.as_str(), string2);
  5. println!("The longest string is {}", result);-
  6. }
  7. fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  8. if x.len() > y.len() {
  9. x
  10. } else {
  11. y
  12. }
  13. }

生命周期标注 - 语法

  • 生命周期的标注不会改变引用的生命周期长度
  • 当指定了泛型生命周期参数,函数可以接收带有任何生命周期的引用
  • 生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期
  • 生命周期参数名:
    • 以 ‘ 开头
    • 通常全小写且非常短
    • 很多人使用 ‘a
  • 生命周期标注的位置:

    • 在引用的 & 符号后
    • 使用空格将标注和引用类型分开

      生命周期标注 - 例子

  • &i32 // 一个引用

  • &’a i32 // 带有显式生命周期的引用
  • &’a mut i32 // 带有显式生命周期的可变引用
  • 单个生命周期标注本身没有意义

    标注之所以存在,是为了向 rust 描述多个泛型生命周期参数之间的关系 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { ... }> longest 函数它有两个参数是两个引用,这两个参数的生命周期都是 ‘a。 这就意味着两个参数的生命周期必须和这个<’a>泛型生命周期存活一样长的时间才可以。

函数签名中的生命周期标注

  • 泛型生命周期参数声明在:函数名和参数列表之间的 <> 里。

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

longest:函数名 (x: &’a str, y: &’a str):参数列表 <’a>:泛型生命周期参数 表达了参数和返回值的这些引用都必须拥有相同的生命周期,而这个生命周期就是 ‘a, 所以就把 ‘a 加入到这些引用里。 这个泛型生命周期 ‘a 得到的具体的生命周期就是 x 和 y 两者中声明周期比较短的那个。而且因为返回值标注的生命周期也是 ‘a,所以返回的引用在x和y比较短的那个生命周期内都是有效的。 接下来看一下生命周期的标注是如何对 longest 函数的调用进行限制,上面代码的编译是没有问题的,修改代码 ```rust fn main() { let string1 = String::from(“abcd”); // String类型 { let string2 = “xyz”; // 字符串字面值类型,也就是字符串切片类型 let result = longest(string1.as_str(), string2); println!(“The longest string is {}”, result); } }

fn longest<’a>(x: &’a str, y: &’a str) -> &’a str { if x.len() > y.len() { x } else { y } }

  1. > 字符串字面值的生命周期是静态的生命周期。在整个程序运行期间都存活。
  2. > &str 是直接在可执行文件中加载,即这块内存直接放到可执行文件里面的,
  3. > 所以整个程序运行期间,这块内存比较特殊,不会改变所有权。
  4. > 所以这块内存的引用,一直会指向这个合法内存,
  5. > 所以其引用的生命周期是 'static,也就是全局静态。
  6. - 生命周期 'a 的实际生命周期是:x y 两个生命周期中较小的那个
  7. ```rust
  8. fn main() {
  9. let string1 = String::from("abcd"); // String类型
  10. let result; // 声明
  11. {
  12. let string2 = String::from("xyz"); // String类型
  13. result = longest(string1.as_str(), string2.as_str());
  14. }
  15. println!("The longest string is {}", result);
  16. }
  17. fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  18. if x.len() > y.len() {
  19. x
  20. } else {
  21. y
  22. }
  23. }
  24. // error[E0597]: `string2` does not live long enough
  25. // --> src/main.rs:6:44
  26. // |
  27. // 6 | result = longest(string1.as_str(), string2.as_str());
  28. // | ^^^^^^^ borrowed value does not live long enough
  29. // 7 | }
  30. // | - `string2` dropped here while still borrowed
  31. // 8 | println!("The longest string is {}", result);
  32. // | ------ borrow later used here
  33. //
  34. // error: aborting due to previous error
  35. //
  36. // For more information about this error, try `rustc --explain E0597`.
  37. // error: could not compile `lifetime1`

longest 的生命周期取的是 x 和 y 这两个引用中生命周期比较短的那个, 具体 string1 的生命周期是2到9行,string2 的生命周期是5到7行, 所以 longest 的泛型生命周期实际上是5到7行

深入理解生命周期

  • 指定生命周期参数的方式依赖于函数所做的事情 ```rust fn main() { let string1 = String::from(“abcd”); let string2 = “xyz”; let result = longest(string1.as_str(), string2); println!(“The longest string is {}”, result); }

fn longest<’a>(x: &’a str, y: &str) -> &’a str { x }

  1. - 从函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期匹配
  2. - 如果返回的引用没有指向任何参数,那么它只能引用函数内创建的值
  3. - 这就是悬垂引用:该值在函数结束时就离开了作用域
  4. ```rust
  5. fn main() {
  6. let string1 = String::from("abcd");
  7. let string2 = "xyz";
  8. let result = longest(string1.as_str(), string2);
  9. println!("The longest string is {}", result);
  10. }
  11. fn longest<'a>(x: &'a str, y: &str) -> &'a str {
  12. let result = String::from("abc");
  13. result.as_str()
  14. }
  15. // error[E0515]: cannot return value referencing local variable `result`
  16. // --> src/main.rs:10:5
  17. // |
  18. // 10 | result.as_str()
  19. // | ------^^^^^^^^^
  20. // | |
  21. // | returns a value referencing data owned by the current function
  22. // 返回的值,它引用的是当前函数内部所持有的数据
  23. // | `result` is borrowed here

想把函数内部创建的值作为返回值返回出去,应该不返回引用,直接返回该值,相当于把所有权移交给函数的调用者。

  1. fn main() {
  2. let string1 = String::from("abcd");
  3. let string2 = "xyz";
  4. let result = longest(string1.as_str(), string2);
  5. println!("The longest string is {}", result);
  6. }
  7. fn longest<'a>(x: &'a str, y: &str) -> String {
  8. let result = String::from("abc");
  9. result
  10. }

从根本上来将,生命周期的语法就是用来关联函数的不同参数以及返回值之间的生命周期,一旦它们之间取得了某种联系,Rust 它就获得了足够的信息,来自支持保障内存安全的操作,并且阻止那些可能会导致悬垂指针或者其他违反内存安全的行为。

struct 定义中的生命周期标注

  • struct 里可包括:
    • 自持有的类型
    • 引用:需要在每个引用上添加生命周期标注 ```rust struct ImportantExcerpt<’a> { part: &’a str, // 字符串切片也就是引用类型所以需要,该引用的生命周期至少要比结构体实例的生命周期要长 }

fn main() { let novel = String::from(“Call me Ishmael. Some years ago…”);

  1. let first_sentence = novel.split(".")
  2. .next()
  3. .expect("Could not found a '.'");
  4. let i = ImportantExcerpt {
  5. part: first_sentence,
  6. };

}

  1. > first_sentence 的生命周期是从第 8 行到第 15 行,而实例 i 是从第 12 行到第 15 行是有效的,
  2. > 所以里面 part 引用的字段它的存活时间要比实例 i 长,并且能完全覆盖 i 的生命周期。
  3. <a name="mxwHR"></a>
  4. ## 生命周期的省略
  5. - 我们知道:
  6. - 每个引用都有生命周期
  7. - 需要为使用生命周期的函数或 struct 指定生命周期参数
  8. ```rust
  9. fn first_word(s: &str) -> &str {
  10. let bytes = s.as_bytes();
  11. for (i, &item) in bytes.iter().enumerate() {
  12. if item == b' ' {
  13. return &s[0..i];
  14. }
  15. }
  16. &s[..]
  17. }

上面代码没有标准任何生命周期,还是可以通过编译,在 Rust 早期版本必须每个引用都要显示的生命周期。 fn first_word<'a>(s: &'a str) -> &'a str在某些特定情况下,同样的生命周期标注,而且这些场景是可以预测的,它有一些明确的模式,于是 Rust 团队就将这些模式,直接写入了编译器代码,就使得进入检查器,在这些情况下,可以自动的对这些生命周期进行推导,而无需显示的标注。了解这段历史是有必要的,因为在未来可能会有更多确定的模式被添加到编译器中,也就是说在未来,需要手动标注生命周期的地方可能会越来越少。

生命周期的省略规则

  • 在 Rust 引用分析中所编入的模式称为生命周期省略规则
    • 这些规则无需开发者来遵守
    • 它们是一些特殊情况,由编译器来考虑
    • 如果你的代码符合这些情况,那么就无需显式标注生命周期
  • 生命周期省略规则不会提供完整的推断:

    • 如果应用规则后,引用的生命周期仍然模糊不清 -> 编译错误
    • 解决办法:手动添加生命周期标注,表明引用间的相互关系

      输入、输出生命周期

  • 生命周期在:

    • 函数/方法的参数:输入生命周期
    • 函数/方法的返回值:输出生命周期

      生命周期省略的三个规则

  • 编译器使用 3 个规则在没有显示标注生命周期的情况下,来确定引用的生命周期

    • 规则 1 应用于输入生命周期
    • 规则 2、3 应用于输出生命周期
    • 如果编译器应用完 3 个规则之后,仍然有无法确定生命周期的引用 -> 报错
    • 这些规则适用于 fn 定义和 impl 块
  • 规则 1:每个引用类型的参数都有自己的生命周期
  • 规则 2:如果只有 1 个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数
  • 规则 3:如果有多个输入生命周期参数,但其中一个是 &self 或 &mut self(是方法),那么 self 的生命周期会被赋给所有的输出生命周期参数

    生命周期省略的三个规则 - 例子

  • 假设我们是编译器:

  • fn first_word(s: &str) -> &str
  • fn first_word<'a>(s: &'a str) -> &str

    应用规则1的效果为每个参数都指定生命周期, 因为只有 1 个输入生命周期,所以规则 2 也适用,> 如果只有 1 个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数

  • fn first_word<'a>(s: &'a str) -> &'a str

    现在这个函数中所有的引用都已经有了生命周期,因此编译器就可以继续分析代码, 而无需程序员来手动标注这个函数里的生命周期了。

例子2:

  • fn longest(x: &str, y: &str) -> &str

    没有标注生命周期,两个输入参数都是引用,返回类型也是引用, 引用规则 1:每个引用类型的参数都有自己的生命周期

  • fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str

    该函数有两个输入生命周期,所以规则 2 不适用,而且 longest 是函数,不是方法, 所以没有 self 参数,规则 3 也不适用。 应用完 3 条规则后,仍然无法计算出返回类型的生命周期,所以编译器就会报错。

方法定义中的生命周期标注

  • 在 struct 上使用生命周期实现方法,语法和泛型参数的语法一样
  • 在哪声明和使用生命周期参数,依赖于:
    • 生命周期参数是否和字段方法的参数返回值有关
  • struct 字段的生命周期名:
    • 在 impl 后声明
    • 在 struct 名后使用
    • 这些生命周期是 struct 类型的而一部分
  • impl 块内的方法签名中:

    • 引用必须绑定于 struct 字段引用的生命周期,或者引用是独立的也可以
    • 声明周期省略规则经常使得方法中的生命周期标注不是必须的
      1. struct ImportantExcerpt<'a> {
      2. part: &'a str,
      3. }
      4. impl<'a> ImportantExcerpt<'a> {
      5. fn level(&self) -> i32 {
      6. 3
      7. }
      8. fn announce_add_return_part(&self, announcement: &str) -> &str {
      9. println!("Attention please: {}", announcement);
      10. self.part
      11. }
      12. }
      13. fn main() {
      14. let novel = String::from("Call me Ishmael. Some years ago...");
      15. let first_sentence = novel.split(".").next().expect("Could not found a '.'");
      16. let i = ImportantExcerpt {
      17. part: first_sentence,
      18. };
      19. }

      静态生命周期

  • ‘static 是一个特殊的生命周期:整个程序的持续时间

    • 例如:所有的字符串字面值都拥有 ‘static 生命周期
    • let s: &'static str = "I have a static lifetime.";

      字符串字面值是被存储在二进制程序中,所以它总是可用的。

  • 为引用指定 ‘static 生命周期前要三思:

    • 是否需要引用再程序整个生命周期内都存活。

      泛型参数类型、trait bound、生命周期

  • 同时使用 泛型参数类型、trait bound、生命周期 的例子 ```rust use std::fmt::Display;

fn longest_with_an_announcement<’a, T>(x: &’a str, y: &’a str, ann: T) -> &’a str where T: Display, { println!(“Announcement! {}”, ann); if x.len() > y.len() { x } else { y } }

fn main() {} ```

ann: 参数类型是泛型类型 T,而根据 where 子句的约束,T 这个类型可以被替换为任何实现了 Display 这个 trait 的类型。