rust之生命周期

rust May 8, 2021

前言

看了好久的死灵书和官网教程,总算可以写这部分了。

为什么需要生命周期

上一篇《rust所有权和生命周期》我们主要讲到了所有权,但是光有所有权不能保证内存安全。对于引用的有效性没办法界定和保证。会导致的以下问题:

  1. 悬挂指针:指针指向的内存空间已经无效了
  2. 数据竞争: 在多线程中,无法保证数据的预期结果。在单一线程中,保证可变引用不会同时修改相同内存。不可变引用的值是一致的,不会被可变引用修改。

rust无法对所有的情况做出推导,需要我们自己去界定一些引用的生命周期。编译器再通过这些标识做引用的有效性检查。

什么是生命周期

生命周期是编译器(引用检查系统)用来确保引用是有效的机制。官网英文定义:

A lifetime is a construct the compiler (or more specifically, its borrow checker) uses to ensure all borrows are valid

我们在rust所有权那篇讲过,引用的使用有两个限制。引用的有效性是通过生命周期来保证的。

  1. 引用的唯一性。
  2. 引用的有效性。

生命周期的语法

引用检查系统使用具体的生命周期注解来决定引用有效性的时间。在生命周期注解不能够省略的地方,Rust要求具体的注解来声明引用的生命周期。
生命周期周期的语法是'+标识,例如声明生命周期周期a   'a

引用的生命周期注解

在引用符号后面加上生命周期声明即可,例如:  x : &'a i32

函数的生命周期注解

foo<'a>(x: &'a i32) 

或者多个注解

foo<'a, 'b>()

方法的生命周期注解

方法的生命周期注解和函数类似


struct Owner(i32);

impl Owner {
    // Annotate lifetimes as in a standalone function.
    fn add_one<'a>(&'a mut self) { self.0 += 1; }
    fn print<'a>(&'a self) {
        println!("`print`: {}", self.0);
    }
}

fn main() {
    let mut owner = Owner(18);

    owner.add_one();
    owner.print();
}

结构体的生命周期注解

结构体和枚举类型的生命周期也是类似的


#[derive(Debug)]
struct Borrowed<'a>(&'a i32);

// Similarly, both references here must outlive this structure.
#[derive(Debug)]
struct NamedBorrowed<'a> {
    x: &'a i32,
    y: &'a i32,
}

// An enum which is either an `i32` or a reference to one.
#[derive(Debug)]
enum Either<'a> {
    Num(i32),
    Ref(&'a i32),
}

fn main() {
    let x = 18;
    let y = 15;

    let single = Borrowed(&x);
    let double = NamedBorrowed { x: &x, y: &y };
    let reference = Either::Ref(&x);
    let number    = Either::Num(y);

    println!("x is borrowed in {:?}", single);
    println!("x and y are borrowed in {:?}", double);
    println!("x is borrowed in {:?}", reference);
    println!("y is *not* borrowed in {:?}", number);
}


trait的生命周期注解

trait的生命周期注解,要在impl关键字后面声明


// A struct with annotation of lifetimes.
#[derive(Debug)]
 struct Borrowed<'a> {
     x: &'a i32,
 }

// Annotate lifetimes to impl.
impl<'a> Default for Borrowed<'a> {
    fn default() -> Self {
        Self {
            x: &10,
        }
    }
}

fn main() {
    let b: Borrowed = Default::default();
    println!("b is {:?}", b);
}

生命周期的省略

在某些情况下,引用检查系统是可以推断出函数/方法中引用的生命周期的,这时候我们可以省略声明生命周期。
存在函数或方法参数中的生命周期声明叫做input lifetimes,存在return处的叫output lifetimes。
存在三个规则下的生命周期声明可以省略:

  1. 规则一:每个参数都有自己的生命周期声明,例如  fn foo<'a>(x: &'a i32) fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
  2. 规则二:只有一个input lifetime,并且这个lifetime作用给所有output lifetimes,例如 fn foo<'a>(x: &'a i32) -> &'a i32
  3. 规则三:如果有多个input lifetimes,但是其中一个是&self,或者是&mut self。并且output lifetimes的生命周期是和self一致的。

[me]:我自己实验发现,方法的output lifetimes默认是和self一致的。如果不声明input lifetimes的生命周期是单独的。