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

After staying up way too late, I was able to come up with an example case where the lifetimes matter. Here is the code:

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

If you were to change the definition of Foo to this:

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

Then the code won't compile.

Basically, if you want to use the fields of the struct on any function that requires it's parameters to have different lifetimes, then the fields of the struct must have different lifetimes as well.


I want to re-answer my question here since it's still showing up high in search results and I feel I can explain better. Consider this code:

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

And the error:

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

What's going on here?

  1. The lifetime of f.x has the requirement of being at least large enough to encompass the scope of x up until the println! statement (since it's initialized with &x and then assigned to v).
  2. The definition of Foo specifies that both f.x and f.y use the same generic lifetime 'a, so the lifetime of f.y must be at least as large as f.x.
  3. But, that can't work, because we assign &y to f.y, and y goes out of scope before the println!. Error!

The solution here is to allow Foo to use separate lifetimes for f.x and f.y, which we do using multiple generic lifetime parameters:

Rust Playground

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

Now the lifetimes of f.x and f.y aren't tied together. The compiler will still use a lifetime that's valid until the println! statement for f.x. But there's no longer a requirement that f.y uses the same lifetime, so the compiler is free to choose a smaller lifetime for f.y, such as one that is valid only for the scope of y.

Tags:

Lifetime

Rust