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?
- The lifetime of
f.x
has the requirement of being at least large enough to encompass the scope ofx
up until theprintln!
statement (since it's initialized with&x
and then assigned tov
). - The definition of
Foo
specifies that bothf.x
andf.y
use the same generic lifetime'a
, so the lifetime off.y
must be at least as large asf.x
. - But, that can't work, because we assign
&y
tof.y
, andy
goes out of scope before theprintln!
. 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
.