In Rust, when we want a struct to contain references, we typically define their lifetimes as such:
struct Foo<'a> {
x: &'a i32,
y: &'a i32,
}
But it's also possible to define multiple lifetimes for different references in the same struct:
struct Foo<'a, 'b> {
x: &'a i32,
y: &'b i32,
}
When is it ever useful to do this? Can someone provide some example code that doesn't compile when both lifetimes are 'a
but does compile when the lifetimes are 'a
and 'b
(or vice versa)?
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?
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
).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
.&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
.
Here is another simple example where the struct definition has to use two lifetimes in order to operate as expected. It does not split the aggregate into fields of different lifetimes, but nests the struct with another struct.
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);
}
The struct Y
has two lifetime parameters, one for its contained field &X
, and one for X
's contained field &z
.
In the operation ((Y(&X(&z))).0).0
, X(&z)
is created as a temporary and is borrowed. Its lifetime is only in the scope of this operation, expiring at the statement end. But since X(&z)
's lifetime is different from the its contained field &z
, the operation is fine to return &z
, whose value can be accessed later in the function.
If using single lifetime for Y
struct. This operation won't work, because the lifetime of &z
is the same as its containing struct X(&z)
, expiring at the statement end; therefore the returned &z
is no longer valid to be accessed afterwards.
See code in the playground.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With