In Rust, the lifetime of a value is between the point of definition and the point of going out of scope.
However the out of scope can be the end of a statement that steals the value.
Rust respects this only partially. Why?
Take this example:
// This is the starting point, it compiles without errors/warnings, but does not do what it should.
struct Inner{x : i32}
struct Outer<'e>{
s : &'e Inner,
c : i32
}
fn print_inner_value(mut o : Outer) {
println!("o.s.x = {:?}", o.s.x);
o.c += 1;
}
fn change_inner(mut o : Outer) {
let new_i = Inner{x : 40};
o.c += 2;
//o.s = &new_i;
print_inner_value(o);
println!("new_i.x = {:?}", new_i.x);
//o.c += 3;
}
fn main () {
let orinal_i = Inner {x : 10};
let mut o = Outer{s : &orinal_i, c : 0};
o.c += 4;
change_inner(o);
}
What I really want is for the line o.s = &new_i;
to not be commented.
But if I do that, I get E0597 saying that new_i does not live long enough.
But it appears to live long enough, because if I instead uncomment o.c += 3;
then I get E0382 saying that o.c cannot be used since it has been moved.
Clearly at println!("new_i.x = {:?}", new_i.x);
line, value new_i
is alive and value o
was moved into a function that has ended so it should no longer be alive.
So the question is: Why moving a value shrinks the scope of it, but does not shrink the lifetime of it?
Your mistake is assuming that the lifetime of 'e
represents the lifetime of Outer
: it doesn't.
Instead, since a value o
of type Outer<'e>
contains a reference with a lifetime of 'e
, then the lifetime of this value o
must be shorter (or equal) to 'e
-- lest the value o
outlives the referred to element, and ends up with a dangling reference.
The real issue you are hitting is that the lifetime parameters of a function ('e
, here) are determined by the caller, and fixed within the function itself.
When you create the instance o
in main
, a lifetime 'e
is calculated matching the lifetime of orinal_i
.
Then, within change_inner
, when you set about changing o.s
, the type system requires that whatever reference you assign must have a lifetime greater than or equal to this predetermined 'e
, that is greater than or equal to the lifetime of orinal_i
.
That the value of o
, containing said reference, will soon disappear has no impact on the required lifetime.
This may be observed better by looking at the non-elided version of the function:
fn change_inner<'e>(mut o: Outer<'e>) {
let new_i = Inner{x : 40};
o.c += 2;
//o.s = &new_i;
print_inner_value(o);
println!("new_i.x = {:?}", new_i.x);
//o.c += 3;
}
The 'e
above is the lifetime of a reference outside the function, thus a lifetime which outlives the function. new_i
, a variable created within the function, has a shorter lifetime than 'e
, and therefore o.s
cannot be bound to &new_i
.
Another way to see it is that scope of o
has an impact on how long orinal_i
is borrowed for, but does not impact the lifetime of orinal_i
: whether o
lives a short or long life, it does not change how long the life of orinal_i
is, just how long o
accesses orinal_i
.
In your particular case, a work-around exists: creating a new Outer
value.
A new value can have a new type, one in which the 'e
parameter is NOT fixed, and the compiler will be able to infer a new "value" for the lifetime, a value shorter than that of &new_i
:
fn change_inner<'e>(o: Outer<'e>) {
let mut neo = o;
let new_i = Inner{x : 40};
neo.c += 2;
neo.s = &new_i;
print_inner_value(neo);
println!("new_i.x = {:?}", new_i.x);
}
Here, the compiler selects a lifetime for neo
's 'e
which works for both o.s
and &new_i
-- the shorter of the two, really.
Do note there's quite a bit of magic at play:
neo = o
to imply that neo
has exactly the same type as o
, yet the compiler infers a different lifetime for neo
's 'e
. This is specific to lifetimes, other generic parameters would be strictly equal. You can see neo = o
as shorthand for neo = Outer { s: o.s, c: o.c }
in this case.neo
outlives new_i
so you'd expect &new_i
not to be assignable to neo.s
. The compiler is free to coalesce lifetimes together, however, and here does so, determining that neo
and new_i
live as long as each other.