什么时候在结构中定义多个生命周期有用?

When is it useful to define multiple lifetimes in a struct?

在 Rust 中,当我们希望结构包含引用时,我们通常会这样定义它们的生命周期:

struct Foo<'a> {
    x: &'a i32,
    y: &'a i32,
}

但也可以为同一结构中的不同引用定义多个生命周期:

struct Foo<'a, 'b> {
    x: &'a i32,
    y: &'b i32,
}

什么时候这样做有用?有人可以提供一些示例代码,当两个生命周期为 'a 时不编译,但在生命周期为 'a'b 时编译(反之亦然)?

在熬夜之后,我想出了一个生命周期很重要的例子。这是代码:

static ZERO: i32 = 0;

struct Foo<'a, 'b> {
    x: &'a i32,
    y: &'b i32,
}

fn get_x_or_zero_ref<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32 {
    if *x > *y {
        return x
    } else {
        return &ZERO
    }
}

fn main() {
    let x = 1;
    let v;
    {
        let y = 2;
        let f = Foo { x: &x, y: &y };
        v = get_x_or_zero_ref(&f.x, &f.y);
    }
    println!("{}", *v);
}

如果您要将 Foo 的定义更改为:

struct Foo<'a> {
    x: &'a i32,
    y: &'a i32,
}

那么代码将无法编译。

基本上,如果您想在任何需要其参数具有不同生命周期的函数上使用该结构的字段,那么该结构的字段也必须具有不同的生命周期。

这是另一个简单示例,其中结构定义必须使用两个生命周期才能按预期运行。它不会将聚合拆分为不同生命周期的字段,而是将结构与另一个结构嵌套。

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

struct Y<'a, 'b>(&'a X<'b>);

fn main() {
    let z = 100;
    //taking the inner field out of a temporary
    let z1 = ((Y(&X(&z))).0).0;  
    assert!(*z1 == z);
}

结构 Y 有两个生命周期参数,一个用于其包含的字段 &X,一个用于 X 的包含字段 &z.

在操作((Y(&X(&z))).0).0中,X(&z)被创建为临时的并被借用。它的生命周期只在这个操作的范围内,在语句结束时到期。但是由于 X(&z) 的生命周期与其包含的字段 &z 不同,因此可以对 return &z 进行操作,稍后可以在函数中访问其值。

如果对 Y 结构使用单一生命周期。此操作将不起作用,因为 &z 的生命周期与其包含的结构体 X(&z) 相同,在语句结束时到期;因此 returned &z 不再有效,以后无法访问。

请参阅 playground 中的代码。

我想在这里重新回答我的问题,因为它在搜索结果中的排名仍然很高,而且我觉得我可以解释得更好。考虑这段代码:

Rust Playground

struct Foo<'a> {
    x: &'a i32,
    y: &'a i32,
}

fn main() {
    let x = 1;
    let v;
    {
        let y = 2;
        let f = Foo { x: &x, y: &y };
        v = f.x;
    }
    println!("{}", *v);
}

错误:

error[E0597]: `y` does not live long enough
--> src/main.rs:11:33
|
11 |         let f = Foo { x: &x, y: &y };
|                                 ^^ borrowed value does not live long enough
12 |         v = f.x;
13 |     }
|     - `y` dropped here while still borrowed
14 |     println!("{}", *v);
|                    -- borrow later used here

这是怎么回事?

  1. f.x 的生命周期要求至少足以包含 x 的范围,直到 println! 语句(因为它是用 &x 然后赋值给 v).
  2. Foo的定义指定f.xf.y都使用相同的泛型生命周期'a,所以f.y的生命周期必须在至少和 f.x.
  3. 一样大
  4. 但是,这行不通,因为我们将 &y 分配给 f.y,并且 yprintln! 之前超出范围。错误!

这里的解决方案是允许 Foof.xf.y 使用单独的生命周期,我们使用多个通用生命周期参数来做到这一点:

Rust Playground

struct Foo<'a, 'b> {
    x: &'a i32,
    y: &'b i32,
}

现在 f.xf.y 的生命周期不再绑定在一起。编译器仍将使用在 f.xprintln! 语句之前有效的生命周期。但是不再要求 f.y 使用相同的生命周期,因此编译器可以自由地为 f.y 选择更小的生命周期,例如仅在 y 范围内有效的生命周期.