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
'erepresents the lifetime ofOuter: it doesn't.Instead, since a value
oof typeOuter<'e>contains a reference with a lifetime of'e, then the lifetime of this valueomust be shorter (or equal) to'e-- lest the valueooutlives 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
oinmain, a lifetime'eis calculated matching the lifetime oforinal_i.Then, within
change_inner, when you set about changingo.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 oforinal_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:
The
'eabove 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 thereforeo.scannot be bound to&new_i.Another way to see it is that scope of
ohas an impact on how longorinal_iis borrowed for, but does not impact the lifetime oforinal_i: whetherolives a short or long life, it does not change how long the life oforinal_iis, just how longoaccessesorinal_i.In your particular case, a work-around exists: creating a new
Outervalue.A new value can have a new type, one in which the
'eparameter 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:Here, the compiler selects a lifetime for
neo's'ewhich works for botho.sand&new_i-- the shorter of the two, really.Do note there's quite a bit of magic at play:
neo = oto imply thatneohas exactly the same type aso, yet the compiler infers a different lifetime forneo's'e. This is specific to lifetimes, other generic parameters would be strictly equal. You can seeneo = oas shorthand forneo = Outer { s: o.s, c: o.c }in this case.neooutlivesnew_iso you'd expect&new_inot to be assignable toneo.s. The compiler is free to coalesce lifetimes together, however, and here does so, determining thatneoandnew_ilive as long as each other.