引用在Rust中与C/C++中的指针一样都拥有相同的地位,但是C/C++中的指针是不安全的,Rust为了解决这个问题,就给程序中的每一个引用类型附加了一个生命期(lifetime)。生命期是程序可以安全使用引用的一个范围,这个范围不一定是一个可见的区域,例如一对 {} 括起来的代码块。

生命期的基本规则

其实除了引用以外,变量也有它自己的生命期,变量的生命期开始于被初始化,结束于离开它们的作用域。那么引用这些变量的引用的生命期,一个最基本的限制就是,这个引用不能比变量的生命期还长寿。例如以下示例。

  1. {
  2. let r;
  3. {
  4. let x = 1;
  5. r = &x;
  6. } // 变量x的生命期到这里就结束了
  7. assert_eq!(*r, 1); // 这里在编译时就会报错
  8. }

在这个示例中,变量 x 的生命期要比引用 r 的生命期短,也就是说,在程序执行到 assert_eq() 的时候,引用 r 已经不知道自己引用的是什么了,因为变量 x 已经超出了它的作用域而被释放了。在这种情况下,引用 r 就变成了悬垂引用。所以Rust才会要求变量的生命期必须要涵盖所有引用它的引用的生命期,也就是说引用 r 的生命期必须要比变量 x 的生命期要短才可以。

之前所说的引用的生命期是一个引用最长的生命期限制,但是引用的生命期也不可能无限制的短。保存在引用变量中的类型,必须保证它在引用的整个生命期内都是有效的。这保证了引用变量中的值在引用的整个生命期内都是有效的。

所以根据这两条基本规则,上面的示例要是想通过编译,需要改为以下样子。

  1. {
  2. let x = 1;
  3. {
  4. let r = &x;
  5. assert_eq(*r, 1);
  6. } // 引用变量r的生命期到这里结束,被变量x的生命期所涵盖
  7. } // 变量x的生命期到这里才会结束

生命期参数

一个函数在使用引用作为其参数的时候,这在Rust中被称为借用,但是这种借用也需要能够保证可以被安全的使用。函数在定义的时候可以使用其完整形式来指定其中引用参数的生命期限制。以下是两个定义生命期限制的函数定义的示例。

  1. fn single_lifetime<'a>(r: &'a i32) { }
  2. fn single_shared_lifetime<'a>(r: &'a i32, s: &'a i32) { }
  3. fn multiple_lifetime<'a, 'b>(r: &'a i32, s: &'b i32) { }
  4. fn multiple_generic_lifetime<'a, 'b, T, R>(r: &'a T, s: &'b R) { }

生命期参数的定义十分类似泛型参数的定义,然而在Rust中,生命期参数和泛型参数都是在尖括号里定义的,只是生命期参数要写在泛型参数前面,例如上面第四个示例函数。

生命期的定义 <'a> 的意义为“对于任意生命期 'a ”,所以相应的函数定义就表示这个函数接受一个任意给定生命期 'a 的调用。由于在调用函数的时候必须允许 'a 为任意生命期,所以一般给定的生命期最短需要包含对函数的调用,也就是说可以认为生命期 'a 是跟函数调用一般长的生命期。

在示例中的第二个函数,它的两个引用参数都使用了相同的生命期 'a ,所以这个函数在调用的时候就必须保证传入函数的两个引用参数都能满足生命期 'a 的要求,且生命期一致。这种单一生命期的写法在Rust中是可以省略生命期定义的,也就是说在默认情况下,函数的所有引用参数的生命期都至少需要覆盖函数的调用。

如果传入函数的参数的生命期不一致,那么仅使用一个生命期参数定义就不能满足要求了,因为所有的参数不能保证拥有一致的生命期。所以就可以如同示例中的第三个函数一样,为不同的参数指定不同的生命期。这样传入函数的参数的生命期就可以被区别对待了。

如果在定义函数时没有声明引用参数的生命期参数,那么Rust会自动为需要生命期参数的位置加上生命期,这个前提是函数中不能返回任何引用。

结构体的生命期参数

与函数不一样,结构体的生命期参数主要是为结构体中所包含的引用属性准备的。如果一个结构体中包含引用属性,但是没有定义这个引用属性的生命期,那么Rust将不会允许程序通过编译的,所以如果要在结构体中使用引用,那么结构体就必须如同下面这个示例中一样定义。

  1. struct Position<'a> {
  2. x: &'a i32,
  3. y: &'a i32
  4. }

在这个示例里面,结构体 Position 的两个属性都是引用属性,并且它们都使用相同的生命期。这个定义就意味着,如果要使用结构体 Position ,那就必须要保证用于初始化结构体属性的值必须至少涵盖结构体 Position 的实例的生命期,并且所有值的生命期需要一致。

如果需要使用生命期不一致的值来初始化结构体引用属性,那么可以定义多个生命期参数。

Rust允许在没有歧义的情况下省略生命期参数。对于结构体中所包含的方法而言,方法的第一个参数 self 都具有与结构体一样长的生命期,并且在默认省略生命期参数的情况下,所有引用参数的生命期都是与 self 参数一样的。

特殊生命期

Rust中还提供了一些特殊的生命期,这些生命期不需要定义或者声明就可以直接使用。

  • 'static ,静态生命期,这个生命期指示与程序运行过程同等长度的生命期。