wisha
wisha

Reputation: 713

Function works with inferred lifetime but not explicit lifetime

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:

Playground link to code

Upvotes: 1

Views: 79

Answers (1)

user4815162342
user4815162342

Reputation: 154906

My understanding is that take_and_return takes ownership of obj, so obj 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 '_ in run_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

Related Questions