linbuxiao / gitblog

1 stars 0 forks source link

Rust Lifetime #10

Open linbuxiao opened 1 year ago

linbuxiao commented 1 year ago

存在的意义

生命周期存在于函数层面,即使我们在定义结构或者字面量时都可以进行声明,但它的意义停留在函数层面。 比如下面的例子:

struct Foo<'a> {
  v: &'a i32
}

static V: i32 = 123;

对于例子 1 的结构体而言,如果我们引用的 v 生命周期短于 Foo 本身,cargo check 会在静态检查时得出报错。 但这不代表这个结构体是错的,只是对于它的函数调用来说,这是不合理的。 总而言之,生命周期的定义是为了函数更好的被调用。

linbuxiao commented 1 year ago

保守的结果

生命周期的存在是 Rust 对于程序运行保守态度的结果。 如果一个函数传入了两个引用,并返回了其中一个引用(或者返回值与其中一个引用是相关的),Rust 就会报错。 因为它困惑于返回值的生命周期,返回值的生命周期与函数接受变量作用域息息相关,Rust 无法判断当前的返回值是否合法。 而是否合法又与悬垂指针相关,这是编译器无法容忍的。 所以你必须事先声明参数与返回值的生命周期,以供 check。 例如:

fn max_num(x: &i32, y: &i32) -> &i32 {
  if x > y {
    &x
  } else {
    &y
  }
}
fn main() {
  let x = 1;                  // -------------+-- x start
  let y = 8;                  // -------------+-- y start
  let max = max_num(&x, &y);  // -------------+-- max start
  println!("max: {}", max);   //              |
}                             // -------------+-- max, y, x over

你会收到错误信息 missing lifetime specifier。即使 x 与 y 的生命周期肉眼可见的一致,但从函数 max_num 的角度看,这两者的生命周期仍然是不确定的。 以避免是如下情况:

fn max_num<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
  if x > y {
    &x
  } else {
    &y
  }
}
fn main() {
  let x = 1;                // -------------+-- x start
  let max;                  // -------------+-- max start
  {                         //              |
    let y = 8;              // -------------+-- y start
    max = max_num(&x, &y);  //              |
  }                         // -------------+-- y over
  println!("max: {}", max); //              |
}                           // -------------+-- max, x over

在这种情况下,我们得到了适当的报错:`y does not live long enough`。 这归功于我们认真书写了生命周期。为自己鼓掌吧。

linbuxiao commented 1 year ago

当生命周期的声明变得复杂

任何类型声明在完善之后都会具有复杂度。这似乎是难以避免的。就连生命周期也难逃此劫。 首先我们拿出上面的例子:

fn max_num<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
  if x > y {
    &x
  } else {
    &y
  }
}

问题来了,如果 x 与 y 的生命周期并不完全一致,那么这里的 'a 意味着什么? 保守的结果,选最短的那个。 那如果这里确实存在多个生命周期,如何表达呢? 生命多个就可以了,如下:

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

当然,如果多个生命周期只是看起来不一样也就失去了意义。 例如这里的写法'b: 'a,就表示 a 的生命周期不能超过 b。被框住了。