跳过正文

生命周期:lifetime

··3958 字
Rust
目录
rust-lang - 这篇文章属于一个选集。
§ 7: 本文

Rust 的 引用类型 都具有 lifetime ,设置 lifetime 的目的是指导 Rust borrow checker 对程序各对象的借用的生命周期进行检查,发现异常时编译报错。

lifetime 只是一个编译时的注解, 没有运行时代表 ,也不能在表达式中使用(指定)。

// error: expected expression, found keyword `dyn`
let b = &'a dyn MyTrait + Send + 'static;

// error: borrow expressions cannot be annotated with lifetimes
let b = &'a(dyn MyTrait + Send + 'static);

lifetime 是一个 相对的约束=Rust borrow checker 根据 lifetime 注解来检查引用是否有效:

  1. <T: 'b> :表示 T 的借用的生命周期比 ‘b 长。
    • &‘b T 隐式表示 T: ‘b, 即 T 的生命周期要比 ‘b 长。
  2. <T: Trait + 'b> :表示 T 要实现 Trait 且 T 的生命周期比 ‘b 长。
  3. struct foo<'a: 'b, 'b,T: 'b> (val1: &'a String, val2: &'a String, val3: &'b String, val4: &T)
    • <‘a: ‘b, ‘b>:表示 ‘a 的生命周期比 ‘b 长;
    • val1 和 val2 的生命周期一样长, 且比 val3 的生命周期长;
    • val4 的生命周期要比 T 长,进而比 ‘b 长,所以 val4 的生命周期要比 val3 长;
    • struct foo 对象的生命周期不能长于 ‘a 和 ‘b;
  4. fn print_refs<'a: 'b, 'b>(x: &'a i32, y: &'b i32) -> &'b String
    • 函数执行期间 ‘a, ‘b 的引用要一直有效,即 ‘a 和 ‘b 的生命周期比函数长;
    • ‘a: ‘b 表示 ‘a 的生命周期比 ‘b 长,所以 x 的生命周期要比 y 长;
    • 返回值的生命周期要和 y 一样长;

lifetime 作为泛型参数时,必须位于其它泛型参数之前,比如 <'a, T, T2>

fn add_ref<'a, 'b, T>(a: &'a mut T, b: &'b T) -> &'a T where T: std::ops::Add<T, Output = T> + Copy,
{
    *a = *a + *b;
    a // OK

    // b // Error: b 的声明周期是 'b 与返回值的声明 'a 不一致。
}

#[derive(Debug)]
struct Ref<'a, T: 'a>(&'a T); // 等效于 struct Ref<'a, T: 'a>(&T);

// 等效于 fn print_ref<'a, T>(t: &T) where T: Debug + 'a {
fn print_ref<'a, T>(t: &'a T) where T: Debug + 'a {
    println!("`print_ref`: t is {:?}", t);
}

struct/enum 的 lifetime:

  • 如果 struct/enum 有 ref 成员,则必须要为 struct/enum 指定 lifetime 参数;
  • struct/enum 对象的 lifetime 要比指定的所有 lifetime 参数
  • 特殊的 struct MyStruct<'static> 则 MyStruct 对象的 lifetime 可以任意长(因为 'static 是在程序整个运行期间都有效);
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);

#[derive(Debug)]
struct NamedBorrowed<'a> {
    x: &'a i32,
    y: &'a i32,
}

#[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);
}

函数 lifetime:

  1. 所有函数参数和返回值,如果是 ref,则必须有 lifetime anno, 如果没有显式指定,Rust 编译器自动加 lifetime anno,规则参考: 3
  2. 所有返回值的 ref 的 lifetime 必须和 某些输入的值的 lifetime 一致 或者是 ‘static;
  3. 如果自动推断后还是不能确定返回值 ref 和输入值 lifetime 的关系,则编译报错,这时需要手动加 lifetime;
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("x is {} and y is {}", x, y);
}

fn failed_borrow<'a>() {
    let _x = 12;

    // ERROR: `_x` does not live long enough
    let _y: &'a i32 = &_x;

    // 这是由于 &_x 的借用有效期持续到函数返回,而 'a 的有效期要长于函数。
}

fn main() {
    let (four, nine) = (4, 9);

    // &four、&nine 的生命周期必须比 print_refs 执行的时间长,也就是在函数执行期间,four 和 nice对
    // 象的引用要一直有效。
    print_refs(&four, &nine);

    failed_borrow(); // 报错。
}

// 'a 的声明周期,即传入的 x 的借用的生命周期要比 print_one 函数长
fn print_one<'a>(x: &'a i32) {
    println!("`print_one`: x is {}", x);
}

fn add_one<'a>(x: &'a mut i32) {
    *x += 1;
}

fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) {
    println!("`print_multi`: x is {}, y is {}", x, y);
}

// 返回值的 lifetime 必须来源于输入参数的 lifetime
fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x }

// 要求返回值的 lifetime 和 'a 一样长, 而函数内的 ref 在函数返回即失效, 所以不能编译.
fn invalid_output<'a>() -> &'a String { &String::from("foo") } // 错误

// 错误的情况,编译器不能推断出返回值引用的 lifetime 关系。
fn order_string(s1 : &str, s2 : &str) -> (&str, &str) {
    if s1.len() < s2.len() {
        return (s1, s2);
    }
    return (s2, s1);
}

如果泛型类型需要 lifetime 参数,但该 Trait 的方法并不需要该 lifetime 参数或则编译器可以自动推断,则可以使用 <'_> :

impl<'a> Reader for BufReader<'a> {
    // 'a is not used in the following methods
}
// 等效于
impl Reader for BufReader<'_> {
}

// 另一个例子自动推导的例子
impl <T> Vec<T> {
    pub fn iter(&self) -> Iter<'_, T> { // 返回的 Iter 的 lifetime 和 &self 一致,编译器自动推导
        // [...]
    }
}

lifetime 之间存在 subtype 的关系 ,即更长的 lifetime 是更短 lifetime 的子类型,所以更长的 lifetime 可以被 coerced 到短一些的 lifetime。

显式声明 lifetime 的关系:

  • 'a: 'b :表示 ‘a lifetime 至少要比 ‘b 长,在返回 ‘b 时可以返回 ‘a 的 lifetime 对象;
  • 'static : 可以被 coerced 到任意的其它 lifetime;
  • HRTB :HRTB 声明的 lifetime 是其它任意 lifetime 的子类型。
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
    first // 'a 被 coreced 到更短的 'b
}

fn main() {
    let first = 2; // Longer lifetime
    {
        let second = 3; // Shorter lifetime

        println!("The product is {}", multiply(&first, &second));
        println!("{} is the first", choose_first(&first, &second));
    };
}

trait MyTrait<'a> {
    fn say_hello(&'a self) -> &'a String;
}

struct MyStruct(String);

impl<'a> MyTrait<'a> for MyStruct {
    fn say_hello(&'a self) -> &'a String {
        println!("hello {}", self.0);
        &self.0
    }
}

fn printf_hello<'a, 'b>(say_hello: Option<&'a (dyn MyTrait<'a> + Send + 'b)>) -> Option<&'b String>
   where 'a: 'b,
{
    let hello = if let Some(my_trait) = say_hello {
        my_trait.say_hello()
    } else {
        return None;
    };

    Some(hello)
}

隐式 liftime bound 的场景:&‘a T 隐式表示 T: ‘a, 即 T 的生命周期比 ‘a 长:

fn requires_t_outlives_a_not_implied<'a, T: 'a>() {}

// OK:T 的生命周期比 'a 长
fn requires_t_outlives_a<'a, T>(x: &'a T) {
    requires_t_outlives_a_not_implied::<'a, T>();
}

// 错误:T 的生命周期不满足比 'a 长的约束
fn not_implied<'a, T>() {
    requires_t_outlives_a_not_implied::<'a, T>();
}

struct Struct<'a, T> {
    field: &'a T, // T 的生命周期比 'a 长
}

enum Enum<'a, T> {
    SomeVariant(&'a T), // T 的声明周期比 'a 长
    OtherVariant,
}

trait Trait<'a, T: 'a> {}

impl<'a, T> Trait<'a, T> for () {} // 错误:T 的声明周期不一定比 'a 长
impl<'a, T> Trait<'a, T> for &'a T {} // OK

使用 std::mem::transmute() 来扩充或缩短 lifetime:

struct R<'a>(&'a i32);

unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> {
    std::mem::transmute::<R<'b>, R<'static>>(r)
}

unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> {
    std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r)
}

1 HRTB (Higher-Rank Trait Bounds)
#

下面没有加 lifetime 的代码是可以正常编译:

struct Closure<F> {
    data: (u8, u16),
    func: F,
}

impl<F> Closure<F> where F: Fn(&(u8, u16)) -> &u8,
{
    fn call(&self) -> &u8 {
        (self.func)(&self.data)
    }
}

fn do_it(data: &(u8, u16)) -> &u8 { &data.0 }

fn main() {
    let clo = Closure { data: (0, 1), func: do_it };
    println!("{}", clo.call());
}

如果要给上面的代码添加 lifetime bound 则会遇到 F 的 lifetime 该如何指定的问题:

struct Closure<F> {
    data: (u8, u16),
    func: F,
}

impl<F> Closure<F> // where F: Fn(&'??? (u8, u16)) -> &'??? u8,
{
    fn call<'a>(&'a self) -> &'a u8 {
        (self.func)(&self.data)
    }
}

fn do_it<'b>(data: &'b (u8, u16)) -> &'b u8 { &'b data.0 }

fn main() {
    'x: {
        let clo = Closure { data: (0, 1), func: do_it };
        println!("{}", clo.call());
    }
}


// Error1:
impl<'a, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a u8,
{
    fn call<'a>(&'a self) -> &'a u8 {
        // lifetime name `'a` shadows a lifetime name that is already in scope
        (self.func)(&self.data)
    }
}

// Error2:
impl<'a, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a u8,
 {
     fn call<'b>(&'b self) -> &'b u8 {
         //  method was supposed to return data with lifetime `'a` but it is returning data
         //  with lifetime `'b`
         (self.func)(&self.data)
     }
 }

// Error3:
impl<'a, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a  u8,
{
    fn call(&self) -> &u8 {
        // 编译器自动为 &self 添加 liefitime 如 '1: method was supposed to return data with
        // lifetime `'1` but it is returning data with lifetime `'a`
        (self.func)(&self.data)
    }
}

// Error4: 可以编译过,但是要求 Closure 的 liefitime 和传入的 Fn 的参数 lifetime 一致,不符合预期
// 语义( Fn 的函数有自己独立的 lifetime,和 Closure 对象 lifetime 无关)。
impl<'a, F> Closure<F> where F: Fn(&'a (u8, u16)) -> &'a  u8,
{
    fn call(&'a self) -> &'a u8 {
        (self.func)(&self.data)
    }
}

// OK:HRTB
// 1. 函数 Closure 泛型参数中没有 'a lifetitme
// 2. 在 F 的 Bound 中使用 for <'a> 来声明 'a lifetime,这里 for <'a> 表示对于任意 lifetime 'a, Fn 都满足
impl<F> Closure<F> where F: for <'a> Fn(&'a (u8, u16)) -> &'a u8,
{
    fn call<'a>(&'a self) -> &'a u8 {
        // 'a 和上面的 for <'a> 没有任何关系,是 call() 方法自己的lifetime。由于编译器会自动加
        // lifetime,所以可以不指定 'a 如:fn call(&self) -> &u8
        (self.func)(&self.data)
    }
}

for <'a> Fn(&'a input u8) 表示 Fn 满足任意 'a liftime ,使用 HRTB 声明的 lifetime 是其它任意 lifetime 的 子类型 ,该 HRTB Fn 可以赋值给其它任意 lifetime 声明的变量。

// HRTB 为闭包的参数和返回值指定 lifetime
fn use_closure<F>(f: F) where F: for<'a> Fn(&'a str) -> &'a str,
{
    let s = "example";
    let result = f(s);
    println!("{}", result);
}

另外,HRTB 也可以用于 fn 指针的 lifetime 声明:

// HRTB 为函数指针的参数和返回值指定 lifetime
fn use_fn_ptr(f: for<'a> fn(&'a str) -> &'a str) {
    let s = "example";
    let result = f(s);
    println!("{}", result);
}
let test_fn: for<'a> fn(&'a _) -> &'a _ = |p: &String| p;
println!("Results:{}", test_fn(&"asdfab".to_string()));

HRTB 等效语法:

where F: for<'a> Fn(&'a (u8, u16)) -> &'a u8,
// 等效为
where for <'a> F: Fn(&'a (u8, u16)) -> &'a u8,

2 ‘static
#

&‘static 表示借用对象在程序运行期间一直有效, 即使 main 函数返回也还有效 ,例如全局 const/static常量,字符串字面量等,它们都保存在程序二进制的 read-only 部分,在程序运行期间一直有效。

let s: &'static str = "hello world";

fn generic<T>(x: T) where T: 'static {}

fn need_static(r : &'static str) {
    assert_eq!(r, "hello");
}

fn main() {
    let v: &'static string = "hello";
    need_static(v);
    println!("Success!")
}

‘static 是任意其它 lieftime 的子类型,所以可以被 coerced 到一个更短的生命周期:

static NUM: i32 = 18;

fn coerce_static<'a>(_: &'a i32) -> &'a i32 {
    &NUM  // &NUM 是 'static lifetime, 所以可以被 coerced 到更短的 'a
}

‘static 的两种解释:

  1. 转移对象所有权时,对应的 Bound 会隐式的具有 ‘static lifetime;
  2. 引用在程序的整个运行期间一直有效,如全局 const/static 常量,或函数内定义的 static 常量;

‘static 只有在引用场景下,才代表引用存在于整个程序运行期间。对象赋值、闭包 move 等语义下, 接收方拥有传入的对象的所有权 ,这时该接收方也实现了 ‘static,如 Box<dyn MyTrait + 'static + Send> 拥有 trait object 的所有权,所以具有 ‘static。

use std::fmt::Debug;

// 函数 hold T 值,所以 T 的 Bound 会隐式的自动加 'static 并自动满足。
fn print_it<T: Debug + 'static>(input: T) {
    println!("'static value passed in is: {:?}", input);
}

// 函数 hold input 值,所以 input 的 Bound 会隐式的加 'static 并自动满足。
fn print_it1(input: impl Debug + 'static) {
    println!("'static value passed in is: {:?}", input);
}

// input 是 ref,借用的 T 值必须是 'static,延续到整个程序的生命周期。
fn print_it2<T: Debug + 'static>(input: &T) {
    println!("'static value passed in is: {:?}", input);
}

fn main() {
    let i = 5;
    print_it(i);

    // i 的 lifetime 持续到 main() 函数返回,而非整个程序声明周期,所以不满两个函数泛型限界的要求。
    print_it(&i); //  `i` does not live long enough
    print_it1(&i); //  `i` does not live long enough

    // but this one WORKS !
    print_it2(&i);
}

Box/String 的 leak() 方法返回一个 ‘static 引用。

// pub fn leak<'a>(self) -> &'a mut str

let x = String::from("bucket");
let static_ref: &'static mut str = x.leak();
assert_eq!(static_ref, "bucket");

3 lifetime elision
#

一般情况下,一个变量的 lifetime 开始于它创建,结束于它被销毁。=Rust borrow checker= 使用 lifetime annotation 检查所有借用操作,从而确保所有的 borrow 都是有效的。

在大部分情况下,由于 elision rule ,用户不需要显式指定 borrow 变量的 lifetime annotation。

// 不需要显式指定 ref lifetime
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

Rust 编译器

  1. The first rule 针对函数的输入参数:is that the compiler assigns a lifetime parameter to each parameter that’s a reference. In other words, a function with one parameter gets one lifetime parameter: fn foo<'a>(x: &'a i32); a function with two parameters gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); and so on.

       struct S<'a, 'b> {
           x: &'a i32,
           y: &'b i32
       }
       fn sum_r_xy(r: &i32, s: S) -> i32 {
           r + s.x + s.y
       }
       // 等效为:
       fn sum_r_xy<'a, 'b, 'c>(r: &'a i32, s: S<'b, 'c>) -> i32
    
  2. The second rule 针对函数的输出参数:is that, if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32.

       fn first_third(point: &[i32; 3]) -> (&i32, &i32) {
           (&point[0], &point[2])
       }
       // 等效为
       fn first_third<'a>(point: &'a [i32; 3]) -> (&'a i32, &'a i32)
    
  3. The third rule 针对方法:is that, if there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters. This third rule makes methods much nicer to read and write because fewer symbols are necessary. 所以,对于方法函数一般不需要指定输入&输出参数的声明周期。

       struct StringTable {
           elements: Vec<String>,
       }
    
       impl StringTable {
           fn find_by_prefix(&self, prefix: &str) -> Option<&String> {
               for i in 0 .. self.elements.len() {
                   if self.elements[i].starts_with(prefix) {
                       return Some(&self.elements[i]); // [i] 返回对象本身,这里需要通过 & 获得它的引用
                   }
               }
               None
           }
       }
    
       // 等效为
       fn find_by_prefix<'a, 'b>(&'a self, prefix: &'b str) -> Option<&'a String>
    

如果经过上面 elision rule,还有引用参数的 lifetime 不明确, Rust 拒绝编译

// OK
fn first_word(s: &str) -> &str {
// 编译器等效为
fn first_word<'a>(s: &'a str) -> &'a str {

fn longest(x: &str, y: &str) -> &str { // 错误,经过 elision rule 后输出引用 lifetime 不明确
// fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {

fn get_str() -> &str; // 错误:不能推断返回值 lifetime

通过给类型指定 '_ lifetime 可以强制使用上面的 lifetime elision 规则。对于 path 的 lifetime,优选 ‘_:

fn new1(buf: &mut [u8]) -> Thing<'_>;                 // elided - preferred
fn new2(buf: &mut [u8]) -> Thing;                     // elided
fn new3<'a>(buf: &'a mut [u8]) -> Thing<'a>;          // expanded

其它函数参数或结果中可以消除 lifetime 的情况:

fn requires_t_outlives_a<'a, T>(x: &'a T) {} // 隐式推导:T: 'a

fn requires_t_outlives_a_not_implied<'a, T: 'a>() {}

fn requires_t_outlives_a<'a, T>(x: &'a T) {
    // OK, 因为 &'a T 隐式表示 T: 'a, 满足函数签名要求
    requires_t_outlives_a_not_implied::<'a, T>();
}

fn not_implied<'a, T>() {
    // Error:因为 T 和 'a 之间的关系不明确
    requires_t_outlives_a_not_implied::<'a, T>();
}

trait object 有特殊的 lifetime bound 规则,参考:[BROKEN LINK: rust-lang/rust-lang-generic-trait.pre-processed.org]

  1. Box<dyn Trait> 等效于 Box<dyn Trait + 'static> , 因为 dyn Trait 对象的所有权被转移到 Box 中;
  2. &'x Box<dyn Trait> 等效于 &'x Box<dyn Trait + 'static> , ‘x 可能是编译器自动加的, 所以即使没有明确指定, &Box<dyn Trait> 等效于 &Box<dyn Trait + ‘static>;
  3. &'r Ref<'q, dyn Trait> 等效于 &'r Ref<'q, dyn Trait+'q>;
rust-lang - 这篇文章属于一个选集。
§ 7: 本文

相关文章

不安全:unsafe
··824 字
Rust
Rust
借用:refer/borrow
··3127 字
Rust
Rust 引用类型和借用
函数、方法和闭包:function/method/closure
··7032 字
Rust
Rust 函数、方法和闭包
包和模块:package/crate/module
··2066 字
Rust
Rust 项目的包和模块组织结构