生命周期

Rust中每一个变量都有生命周期(lifetime), 它表示这个变量的有效作用范围

大部分情况下生命周期是可以由编译器推断出来的, 和类型推断类似. 当有多种类型可能的时候也需要注明类型, 生命周期也是一样. Rust使用范型生命周期参数来注明引用之间的关系, 这样可以确保运行时世纪使用的引用总是有效的.

生命周期的主要目的是防止空指针引用, 它会导致程序出现非预期的数据引用

Try in playground

Rust展开
123456789101112131415161718192021
fn main() {
  let a ;
  {
    let b = 0; 
    a = &b;
  }

  println!("a is {}", a); 
}

/********************output*******************/
error[E0597]: `b` does not live long enough
 --> src/main.rs:5:9
  |
5 |     a = &b;
  |         ^^ borrowed value does not live long enough
6 |   }
  |   - `b` dropped here while still borrowed
7 | 
8 |   println!("a is {}", a); 
  |                       - borrow later used here

报错的原因已经在错误中指出, 在第6行变量b已经被销毁, 但外层变量 a 依然存在, 但它有引用到内层作用域的b, 因此a说指向的数据已经是无效的了(空指针), 再对 a 进行操作就无法保证它的行为是符合预期的, 因此Rust拒绝编译这段代码.

Rust通过 借用检查器(brorow checker) 来保证这一行为, 通过借用检查器, Rust可以获取每一个变量的生存期, 其分析结果类似于:

Try in playground

Rust展开
123456789
fn main() {                   //
  let a ;                     //-----------------+ 'a
  {                           //                 |
    let b = 0;                //---------+'b     |
    a = &b;                   //         |       |
  }                           //---------+       |
                              //                 |
  println!("a is {}", a);     //                 |
}                             //-----------------+

当一个长生命周期的变量引用一个短生命周期变量的时候, 借用检查器便会拒绝编译.

当我们定义函数时, 并不知道参数传入值的具体生命周期, 有时可能需要明确参数的生命周期

Try in playground

Rust展开
123456789101112131415161718192021222324252627
fn main(){
    let a = 1;
    let b = 2;
    let r = max(&a, &b);
    println!("{r} is bigger!");
}

fn max(a: &i32, b: &i32)->&i32{
  if a > b {
    return a;
  }else{
    return b;
  }
}

/************************output***************************/
error[E0106]: missing lifetime specifier
 --> src/main.rs:8:27
  |
8 | fn max(a: &i32, b: &i32)->&i32{
  |           ----     ----   ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
  |
8 | fn max<'a>(a: &'a i32, b: &'a i32)->&'a i32{
  |       ++++     ++          ++        ++

错误中已经指出我们需要给max 函数的参数加上 lifetime parameter

通过函数签名指定生命周期注解时, 并不会改变传入函数数据的实际生命周期, 而是要求借用检查器拒绝编译任何不遵守这个生命周期约定的代码.

借用检查器在处理时max函数时并不需要知道参数 a b 具体会存在多久, 只需要确定 如果 参数a 的生存期时 'a 那么参数 b和返回值的生存期至少也是 'a, 反过来也是一样.

在实参传递给 max 函数时, 范型 'a 所代表的具体生命周期是变量 a和b 所重叠的那部分作用域. 换句话说就是他们俩较小的那部分. 那么返回值的生命周期就表明返回值在参数a b生命周期较短的那个变量被销毁前是有效的.

Try in playground

Rust展开
123456789101112131415161718192021222324
fn main(){
    let a = 1;
    let r;
    {
      let b = 2;
      r = max(&a, &b);
    }
    println!("{r} is bigger!");
}

fn max<'a>(a: &'a i32, b: &'a i32)->&'a i32{
  if a > b { a }else{ b }
}

/*****************output*********************/
error[E0597]: `b` does not live long enough
 --> src/main.rs:6:19
  |
6 |       r = max(&a, &b);
  |                   ^^ borrowed value does not live long enough
7 |     }
  |     - `b` dropped here while still borrowed
8 |     println!("{r} is bigger!");
  |                - borrow later used here

可以这么理解, 函数的返回的数据引用需要与至少一个参数的生命周期关联, 如果不能建立这种关联则函数的返回值一定是函数内部产生的数据, 这将产生空指针引用.

结构体中注解生命周期

Rust展开
1234
struct People<'a> {
    name: &'a str,
    age: u8
}

这个注解表明 结构体People 的实例 不能比它的name属性的引用存在的更久. 也就是如果name 属性引用的数据被销毁了, 则这个实例也将不再可用.

生命周期省略规则

在某些特定的情况下, 生命周期是可以省略的, Rust 可以基于一些约定的规则推导出生命周期, 这些规则称为生命周期省略规则

函数或方法的参数的生命周期被称为输入生命周期(input lifetimes), 返回值的生命周期被称为输出生命周期 (output lifetimes).

  1. 每一个引用类型的参数都有它自己的生命周期参数

    fn max<'a>(a: &'a i32)

    fn max<'a, 'b>(a: &'a i32, b: &'b i32)

  2. 如果只有一个输入生命周期参数则它被赋予所有的输出生命周期

fn max<'a>(a: &'a i32)->&'a i32

  1. 如果函数或方法的参数中有一个是&self 或者 &mut self则所有的输出生命周期参数被赋予self的生命周期.

第一条规则适用于输入生命周期, 第二条第三条适用于输出生命周期, 如果检查器在适用则这三条规则之后依然不能计算出生命周期的引用, 检查器将会抛出错误.

方法中注解生命周期

当带有生命周期的结构体实现方法时, 需要同步为方式注解生命周期. 注解生命周期的位置取决于生命周期参数是否同结构体的属性或方法参数、返回值有关.

Rust展开
12345678910111213
impl<'a> People<'a> {

    fn age(&self)->u8{
        self.age
    }
    fn name(&self) -> &'a str {
      self.name
    }
    fn set_name(&mut self, name: &'a str) -> &'a str {
      self.name = name;
      return self.name
    }
}

静态生命周期

'static是一个特殊的生命周期, 它表示注解的变量生存于程序的运行期间

所有的字符串字面值都有'static生命周期

Rust展开
1
let s :&'static str = "roadup";

这个字符串被字节硬编码进二进制的文件中, 而这个文件中是可用的, 因此它的生命周期是程序运行的全部时间, 也就是'static.

有时候编译器会给出添加'static 生命周期的建议, 在接受建议之前需要确认这个引用是否真的符合'static的生命周期. 大部分情况下, 代码中的问题是在尝试创建一个空指针引用或者可用的证明周期不匹配, 需要解决这问题而不是指定'static.

给引用加上生命周期注解并不能改变变量的生存期, 生命周期注解的主要作用是为借用检查器提供引用之间的生存期关系, 使得检查器能确保引用的有效性, 从而拒绝有安全风险的代码.

Try in playground

Rust展开
1234567891011121314151617181920212223242526272829
#[derive(Debug)]
struct People<'a> {
    name: &'a str,
    age: u8
}

fn main() {
    let p :&'static mut People<'static> = &mut People{
      name: "roadup",
      age: 18
    };
    println!("{p:?}");
}

/**************************output********************************/
error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:8:48
   |
8  |       let p :&'static mut People<'static> = &mut People{
   |  ____________----------------------------________^
   | |            |
   | |            type annotation requires that borrow lasts for `'static`
9  | |       name: "roadup",
10 | |       age: 18
11 | |     };
   | |_____^ creates a temporary which is freed while still in use
12 |       println!("{p:?}");
13 |   }
   |   - temporary value is freed at the end of this statement

- roadup -