(九)Rust_生命周期


生命周期

什么是生命周期

  1. Rust 每个引用都有自己的生命周期。

  2. 生命周期:引用保持有效的作用域。

  3. 大多数情况:生命周期是隐式的、可被推断的。

  4. 当引用的生命周期可能以不同的方式互相关联时:手动标注生命周期。

  5. 生命周期的作用:

    • 主要作用就是为了避免悬垂引用(dangling reference)。
    /*
     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 |
     9 |         println!("r : {}", r);
     |                            - borrow later used here
    */
    
     let r;
     {
         let x = 5;
         r = &x;
     }
    
     println!("r : {}", r);
    
  6. 借用检查器:

    • Rust 实现生命周期检查的主要方式是借用检查器具:比较作用域来判断所有的借用是否合法。

泛型生命周期

函数中的范型生命周期

  1. 函数标注生命周期。

    fn main() {
        let string1 = String::from("abcd");
        let string2 = "xyz";
    
        let result = longest(string1.as_str(), string2);
    
        println!("The longest string is {}", result);
    }
    
    // longest 的返回值会是 x、y 其中之一,x、y 的具体生命周期也无法确定
    // 而且与函数的实现体无关,rust 只检查函数签名
    // 需要手动增加生命周期。
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }
    
  2. 生命周期的标注:

    • 生命周期的标注不会改变引用的生命周期长度。
    • 当指定了泛型生命周期参数,函数可以接收带有任何生命周期的引用。
    • 生命周期的标注:描述了多个引用的生命周期间的关系,但不影响生命周期。
  3. 生命周期的标注语法:

    • 生命周期参数名:
      • ' 开头。
      • 通常全小写且非常短。
      • 通常使用 'a
    • 生命周期标注的位置:
      • 在引用的 ‘&’ 符号后。
      • 使用空格将标注和引用类型分开。
  4. 函数签名中的生命周期标注:

    • 泛型生命周期参数声明在:函数名和参数列表之间的 <> 里。
  5. 生命周期的约束如何生效:

    • 当 string1、string2 的生命周期不在一致,且返回值 result 生命周期长度大于 string2 时,编译报错。
    • 生命周期 'a 的实际生命周期是:x 和 y 两个生命周期中较小的哪个。
    /*
    error[E0597]: `string2` does not live long enough
    --> src/main.rs:7:44
    |
    7 |         result = longest(string1.as_str(), string2.as_str());
    |                                            ^^^^^^^^^^^^^^^^ borrowed value does not live long e
    nough
    8 |     }
    |     - `string2` dropped here while still borrowed
    9 |     println!("The longest string is {}", result);
    |                                          ------ borrow later used here
    
    */
    fn main() {
        let result;
        let string1 = String::from("abcd");
        {
            let string2 = String::from("xyz"); //这里改成 String 是由于 静态字符串非堆内存 j
    
            result = longest(string1.as_str(), string2.as_str());
        }
        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. 指定生命周期参数的方式依赖于函数所做的事情。

    • 如下例中,返回的只有 x,那么参数 y 就不再需要生命周期的标注,只需要保证 x 与返回值 str
      切片的生命周期一致即可。
    fn longest<'a>(x: &'a str, y: &str) -> &'a str {
        x
    }
    
  2. 当函数返回引用时,返回类型的生命周期参数需要与其中一个参数的生命周期匹配。

  3. 如果返回的引用没有指向任何参数,那么它就只能引用函数内创建的值:

    • 这就是悬垂引用:该值在函数结束时就走出里作用域。

Struct 定义中的生命周期标注

  1. Struct 里可包括:

    • 自持有的类型。
    • 引用:需要在每个引用上添加生命周期标注。
    // part 所引用的对象 str 的生命周期需要包括整个 struct 实例的生命周期。
    struct ImportantExcerpt<'a> {
        part: &'a str,
    }
    
    fn test_struct_lifttime() {
        let novel = String::from("Call me Ishmael. Some years ago ...");
        let first_sentence = novel.split('.').next().expect("Could not found a .");
        let i = ImportantExcerpt {
            part: first_sentence,
        };
    
        println!("print part {}", i.part);
    }
    

生命周期的省略

  1. 经过前文我们了解到,每个引用都有生命周期,需要为使用生命周期的函数或 struct 指定生命周期参数。

    • 但是我们之前在 02 节所有权中又一个例子返回了引用却没有标注生命周期,这就是生命周期的省略。
    fn first_world_v2(s: &String) -> &str {
        let bytes = s.as_bytes();
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return &s[..i];
            }
        }
        &s[..]
    }
    
  2. 生命周期的省略规则:在 Rust 引用分析中所编入的模式称为生命周期省略规则

    • 这些规则无需开发者来遵守。
    • 它们是一些特殊情况,由编译器来考虑。
    • 如果你的代码符合这些情况,那么就无需显式的标注生命周期。
  3. 生命周期省略规则不会提供完整的推断:

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

输入生命周期与输入生命周期

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

生命周期省略的三个规则

  1. 规则 1 应用于输出生命周期。
  2. 规则 2、3 应用于输入生命周期。
  3. 这些规则适用于 fn 定义和 impl 块。
  • 规则 1:每个引用类型的参数都有自己的生命周期。
  • 规则 2:如果只有 1 个输入生命周期参数,那么该生命周期被赋给所有的输出生命周期参数。
  • 规则 3:如果有多个生命周期参数,但其中一个是 &self&mut self(是方法),那么 self
    的生命周期会被赋给所有的输出生命周期参数。

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

  1. 在 struct 上使用生命周期实现方法,语法和泛型参数的语法一样。

  2. 在哪生命和使用生命周期参数,依赖于:

    • 生命周期参数是否和字段、方法的参数或返回值有关。
  3. struct 字段的生命周期名:

    • impl 后声明。
    • struct 名子后使用。
    • 这些生命周期是 struct 类型的一部分。
  4. impl 块内的方法签名中:

    • 引用必须绑定于 struct 字段引用的生命周期,或者引用是独立的也可以。
    • 生命周期省略规则经常使得方法中的生命周期标注不是必须的。
    struct ImportantExcerpt<'a> {
        part: &'a str,
    }
    
    impl<'a> ImportantExcerpt<'a> {
        fn level(&self) -> i32 {
            3
        }
        // 返回的引用被赋予来 self 的生命周期
        fn announce_and_return_part(&self, announcement: &str) -> &str {
            println!("Attention please: {}", announcement);
            self.part
        }
    }
    

静态生命周期

  1. 'static 是一个特殊的生命周期:整个程序的持续时间。

    • 例如:所有的字符串字面值都拥有 'static 生命周期。
      • let s: &'static str = "I have a static lifetime";
  2. 为引用指定 'static 生命周期前要三思:

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

例子

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

文章作者: Layton
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Layton !
  目录