Reputation: 713
In this code:
struct Obj<'a> {
inside: &'a mut i32
}
fn take_and_return<'o>(obj: Obj<'o>) -> Obj<'o> {
obj
}
fn run_me_1() {
let mut v = 42;
let s: Obj<'_> = Obj {
inside: &mut v
};
take_and_return(s);
}
I wanted to introduce named lifetime for s
in run_me_1
.
I used Rust Analyzer's suggestion:
fn run_me_2<'a>() {
let mut v = 42;
let s: Obj<'a> = Obj {
inside: &mut v
};
take_and_return(s);
}
But then I got the following error:
error[E0597]: `v` does not live long enough
--> src/lib.rs:20:11
|
17 | fn run_me_2<'a>() {
| -- lifetime `'a` defined here
18 | let mut v = 42;
19 | let s: Obj<'a> = Obj {
| ------- type annotation requires that `v` is borrowed for `'a`
20 | inside: &mut v
| ^^^^^^ borrowed value does not live long enough
...
23 | }
| - `v` dropped here while still borrowed
My understanding is that take_and_return
takes ownership of obj
, so obj
must last forever, so 'o
must last forever. That explains why run_me_2
fails to compile.
My questions are:
run_me_1
compile?'_
in run_me_1
?run_me_2
so that it compiles?Upvotes: 1
Views: 79
Reputation: 154906
My understanding is that take_and_return takes ownership of
obj
, soobj
must last forever, so'o
must last forever.
That kind of statement is true if the value is owned and has a 'static
lifetime bound, typically expressed as T: 'static
. take_and_return
doesn't require T: 'static
, it takes a concrete type associated with a lifetime 'o
which it must not outlive (because it contains a reference with that lifetime).
run_me_1
compiles not because the object is static, but because the variable v
clearly outlives the value returned by take_and_return
, which is immediately dropped, while v
is still live. If you modified run_me_1
so that the value returned by take_and_return()
actually outlived v
, it would fail to compile too. For example:
fn run_me_1_modified() {
let x;
{
let mut v = 42;
let s: Obj<'_> = Obj { inside: &mut v };
x = take_and_return(s);
}
println!("{:p}", x.inside); // or use x in any other way
}
What did the inferrer put into that
'_
inrun_me_1
?
It put an anonymous lifetime corresponding to the part of the source where v
is live.
run_me_2()
is a different beast. It basically says, "I will let my caller choose any lifetime it wishes, and then I'll create an Obj
associated with that lifetime, after which I'll pass it to take_and_return()
, get an Obj
with the same lifetime back, and destroy it. This can't be right because Obj { &mut v }
clearly requires that the lifetime specified by Obj
be a subset of the lifetime of v
. But we're technically allowing our caller to choose the lifetime of obj
, so it might as well call us with run_me_2<'static>
.
Note that a declaration like fn run_me_2<'a>() { ... }
doesn't make too much sense because only the static lifetime can be named without referring to a previously defined value. Normally lifetimes are connecting to existing values. The canonical example is something like:
fn search<'a, 'b>(haystack: &'a str, needle: &'b str) -> Option<&'a str>
...which says that the return value will be coming from haystack
, and not from needle
. On the other hand, fn run_me_2<'a>()
is almost useless because it doesn't allow the caller to choose a useful lifetime based on a value it cares about, but only to provide 'static
as the only possible name.
How can I fix
run_me_2
so that it compiles?
If you insist on having a named lifetime for Obj
, then you need to introduce a function that receives a value connected to that lifetime. For example, you could pass the reference to the variable to an inner function:
fn run_me_2() {
fn inner_fn<'a>(r: &'a mut i32) {
let s: Obj<'a> = Obj {
inside: r,
};
take_and_return(s);
}
let mut v = 42;
inner_fn(&mut v);
}
Upvotes: 3