何时在结构中定义多个生命周期是有用的?

Kai*_*Kai 30 lifetime rust

在Rust中,当我们想要一个包含引用的结构时,我们通常会定义它们的生命周期:

struct Foo<'a> {
    x: &'a i32,
    y: &'a i32,
}
Run Code Online (Sandbox Code Playgroud)

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

struct Foo<'a, 'b> {
    x: &'a i32,
    y: &'b i32,
}
Run Code Online (Sandbox Code Playgroud)

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

Kai*_*Kai 20

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

铁锈游乐场

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);
}
Run Code Online (Sandbox Code Playgroud)

和错误:

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
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

  1. 的生存期f.x要求至少足够大以涵盖x直到println!语句为止的范围(因为它是用 初始化&x然后分配给v)。
  2. 的定义Foo指定 和 都f.x使用f.y相同的通用生命周期'a,因此 的生命周期f.y必须至少与 一样大f.x。
  3. 但是,这是行不通的,因为我们分配&y给f.y, 并y在 之前超出了范围println!。错误!

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

铁锈游乐场

struct Foo<'a, 'b> {
    x: &'a i32,
    y: &'b i32,
}
Run Code Online (Sandbox Code Playgroud)

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


Kai*_*Kai 15

在熬夜之后,我能够提出一个生命时间重要的案例.这是代码:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

struct Foo<'a> {
    x: &'a i32,
    y: &'a i32,
}
Run Code Online (Sandbox Code Playgroud)

然后代码将无法编译.

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

  • 哈哈哈哈!我或多或少地写了完全相同的东西,然后在15分钟前发生了电源故障.我只是*即将发布它.是的,关于我能想到的唯一情况是你希望能够在使用它之后获取一个聚合值并拆分它的一部分,而不会丢失生命周期信息.考虑构建一组值(可能涉及生命周期),使用它,然后恢复原始值. (6认同)
  • 说一个函数"需要"其参数具有不同的生命周期是没有意义的.生命周期参数的目的是防止函数或结构*将这些参数统一为单个(推断的)生命周期,因此借用检查器可以区分它们 (4认同)
  • get_x_or_zero_ref中的'b'当然可以省略,因为默认的生命周期省略规则暗示了它. (3认同)

Xia*_* Li 9

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

看到代码操场。