[rust]生命周期

2 阅读4分钟

介绍

  1. 生命周期是一个描述引用有效作用域的注解,它用来告知编译器不同引用在函数或方法间传递的关系及其有效时间段,并确保所有引用在其作用域内是有效的,其主要目的是为了避免悬垂引用
  2. 函数或者方法参数的生命周期称为输入生命周期,返回值的生命周期称为输出生命周期
  3. 生命周期注解语法:撇号'加一个名字,例如通常定义为'a,当然你想要定义为'hello也行
  4. 因为一些情况下生命周期是隐含且可推断的,所以不必显示声明,正如大部分的数据类型可推断一样

悬垂引用的案例:

fn main() {
    let x;
    {
        let y = String::from("hello");
        x = &y
    }
    println!("{}", x);
}

变量y被借用后在第6行代码执行就就被drop掉了,生命周期结束,但第7行变量x还在使用被借用的值,导致报错

➜  hello_rust git:(master) ✗ cargo run
   Compiling hello_rust v0.1.0 (/Users/kl/rust/hello_rust)
error[E0597]: `y` does not live long enough
 --> src/main.rs:5:13
  |
4 |         let y = String::from("hello");
  |             - binding `y` declared here
5 |         x = &y
  |             ^^ borrowed value does not live long enough
6 |     }
  |     - `y` dropped here while still borrowed
7 |     println!("{}", x);
  |                    - borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `hello_rust` (bin "hello_rust") due to 1 previous error
➜  hello_rust git:(master) ✗

函数的生命周期

错误的示例

fn longest(s1: &str, s2: &str) -> &str {
    return if s1.len() > s2.len() {
        s1
    } else {
        s2
    };
}

编译报错:

  1. 传入的s1和s2都是引用,返回值也是引用
  2. 编译器需要保证返回的引用,其生命周期一定是和传入引用的生命周期是相同的,否则就可能出现悬垂引用
error[E0106]: missing lifetime specifier
 --> src/main.rs:3:35
  |
3 | fn longest(s1: &str, s2: &str) -> &str {
  |                ----      ----     ^ 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 `s1` or `s2`
help: consider introducing a named lifetime parameter
  |
3 | fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
  |           ++++      ++           ++          ++


正确示例:

fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    return if s1.len() > s2.len() {
        s1
    } else {
        s2
    };
}


fn main() {
    let s1 = String::from("hello");
    let s2 = String::from("hi");
    let str = longest(s1.as_str(), s2.as_str()); 
    // 或者let str = longest(&s1, &s2);
    println!("{}", str)
}

结构体的生命周期

#[derive(Debug)]
struct User<'a>{
    name: &'a str,
}

fn main() {
    let user = User { name: "Tom" };
    println!("{:?}",user)
}

方法的生命周期

错误示例

struct MyStruct<'a> {
    field: &'a str,
}

impl<'a> MyStruct<'a> {
    fn get_ref(&self, s1: &str, s2: &str) -> &str {
       s1
    }
}

编译报错:

  1. 编译器默认将self的生命周期赋予给输出生命周期
  2. 实际我们使用了s1的生命周期,和编译器自动推导的结果不一样,所以需要显式注解s1
error: lifetime may not live long enough
  --> src/main.rs:34:8
   |
33 |     fn get_ref(&self, s1: &str, s2: &str) -> &str {
   |                -          - let's call the lifetime of this reference `'1`
   |                |
   |                let's call the lifetime of this reference `'2`
34 |        s1
   |        ^^ method was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`
   |
help: consider introducing a named lifetime parameter and update trait if needed
   |
33 |     fn get_ref<'b>(&self, s1: &'b str, s2: &str) -> &'b str {
   |               ++++             ++                    ++

正确示例: 方法中另外定义的生命周期注解声明不能和结构体中的注解声明一样,否则会报错lifetime 'a already in scope

struct MyStruct<'a> {
    field: &'a str,
}

impl<'a> MyStruct<'a> {
    fn get_ref<'b>(&self, s1: &'b str, s2: &str) -> &'b str {
       s1
    }
}

生命周期标注省略

Rust编译器根据以下三条规则来自动推断生命周期,从而使得在编写代码时不必为了某些常见的情况显示指定生命周期注解,简化代码

  1. 函数输入生命周期自动继承:当一个函数只有一个输入生命周期参数时,这个生命周期会自动被赋予给所有的输出生命周期参数
  2. 函数多参数中引用的生命周期:当一个函数有多个输入生命周期参数,但其中只有一个是引用类型时,那个唯一的生命周期会被赋予给所有的输出生命周期参数
  3. 方法的输入生命周期:当方法有多个输入生命周期参数,且其中包含&self&mut self,则self的生命周期会被赋予给所有输出生命周期参数

规则一示例

// 这里的生命周期 'a 被自动推断为输入生命周期 'a
fn get_return<'a>(x: &'a str) -> &'a str {
    x
}

等价于

fn get_return(x: &str) -> &str {
    x
}

规则二示例

// 这个函数接收两个参数,但只有第一个是引用类型
// 生命周期 'a 被自动推断
fn print_and_return<'a>(x: &'a str, y: i32) -> &'a str {
    println!("{}", y);
    x
}

等价于

fn print_and_return(x: &str, y: i32) -> &str {
    println!("{}", y);
    x
}

规则三示例

struct MyStruct<'a> {
    field: &'a str,
}

// 这里的 self 是方法的一个输入参数,生命周期 'a 被自动推断
impl<'a> MyStruct<'a> {
    fn get_ref(&'a self) -> &'a str {
        self.field
    }
}

等价于

struct MyStruct<'a> {
    field: &'a str,
}

impl<'a> MyStruct<'a> {
    fn get_ref(&self) -> &str {
        self.field
    }
}

静态生命周期

  1. 其生命周期存活于整个程序期间,所有的字符字面值都拥有static生命周期
  2. 定义方式:'static
fn main() {
   let s: &str = "hello"; 
    // 等价于
   let s: &'static str = "hello";
}