Lifetime inferred to longer lifetime than required

The below code is a reproduction of an issue I have in my project.

Playground

struct Holder<'a> {
    inner: Inner<'a>,
}

struct Inner<'a> {
    func: Box<dyn Fn() -> &'a str + 'a>,
}

impl<'a> Holder<'a> {
    fn example(self) {
        let Self { inner } = self;
        let tmp = String::new();
        foo(inner, tmp.as_str());
        //         ^^^^^^^^^^^^ borrowed value does not live long enough
    }
}

fn foo<'a>(inner: Inner<'a>, s: &'a str) {
    todo!()
}

I have a situation where the lifetime of both inner and tmp appears to be suitable for the function call foo however the Rust compiler infers the required lifetime to be that of the current lifetime of inner which is obviously longer than the lifetime of tmp.

  1. Why does the compiler require the lifetime of tmp to be longer than what I am expecting?
  2. Is there a way for me to tell the compiler to use the lifetime of tmp when calling the function?
  3. Why is this only a problem with dyn Fn? If func was an ordinary reference this doesn't appear to be a problem. Has it got something to do with Drop?

Changing foo to the following works

fn foo<'a, 'b>(inner: Inner<'a>, s: &'b str)
where
    'a: 'b,
{
    todo!()
}

When you have lifetime on a type, it will need to outlive the type. Which implies that the loan has to be created before the instance of the type is created.

So you can't have self with 'a exist first, and then make something borrowed for 'a. The inner scope of your fn example will always be smaller than the outer scope of impl<'a>.

When you have &'b str and 'a: 'b, then it does allow the string to have a different, shorter lifetime than 'a.

1 Like

Then why does the following work fine?

Playground

struct Holder<'a> {
    inner: Inner<'a>,
}

struct Inner<'a> {
    func: Box<dyn Fn() -> String + 'a>,  // changed return type here
}

impl<'a> Holder<'a> {
    fn example(self) {
        let Self { inner } = self;
        let tmp = String::new();
        foo(inner, tmp.as_str());
    }
}

fn foo<'a>(inner: Inner<'a>, s: &'a str) {
    todo!()
}

Shared lifetimes also have variance, which I think kicks in in this case, and shortens Inner<'a> + &str to Inner<'shorter> + &'shorter str, effectively giving you:

fn foo<'temp>(inner: Inner<'temp>, s: &'temp str) 

If you change the code to force foo's lifetime to be the same as the outer <'a>:

fn example(self) -> Inner<'a> 
…
fn foo<'a>(inner: Inner<'a>, s: &'a str) -> Inner<'a> {
    inner
}

You will see the value returned from foo doesn't actually match the lifetime 'a.

The important thing here is to note that lifetimes don't make anything live longer. They only describe what the code does anyway, and/or limit it further. You tmp will only live inside example.

4 Likes

Are you saying that because the variance of each of the following is different is why it results in different behaviour when foo is called?

dyn Fn() -> &'a str + 'a
dyn Fn() -> String + 'a

Yes, the output of a Fn trait is effectively an associated type, and that's invariant.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.