Lifetime of function pointer is for<'a, '_> while it should be for<'r>

Sometimes I struggle with lifetimes. I'm still learning and I don't know what's happening here:

use std::future::Future;
use futures::future::{BoxFuture, FutureExt};

struct M{}
struct Client{}

impl Client {
    async fn send_and_expect<'a>(
        &'a mut self,
        m: &M
    ) -> std::result::Result<(), ()> {
        Ok(())
    }
    
    pub fn connection_retrier<'a, T>(
        f: fn(&'a mut Self, &M) -> T,
        f_self: &'a mut Self,
        f_m: &'a M,
    )-> BoxFuture<'a, std::result::Result<(),()>>
    where
        T: Future<Output = std::result::Result<(), ()>> + 'a
    {
        async move {
            Client::send_and_expect(f_self, f_m).await
        }.boxed()
    }
    
    async fn send_with_retry<'a>(&'a mut self) -> std::result::Result<(), ()> {
        let m = M{};
        Client::connection_retrier(
                    Client::send_and_expect, 
                    &mut *self, &m).await
    }
}

Error:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/lib.rs:31:21
   |
11 |     ) -> std::result::Result<(), ()> {
   |          --------------------------- the `Output` of this `async fn`'s found opaque type
...
31 |                     Client::send_and_expect, 
   |                     ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected fn pointer `for<'r> fn(&mut Client, &'r M) -> impl futures::Future`
              found fn pointer `for<'a, '_> fn(&'a mut Client, &M) -> impl futures::Future

Playground

I'm now completely confused about why in for<'r> fn(&mut Client, &'r M) -> impl futures::Future, &mut Client has no lifetime. And what does _ means in for<'a, '_> fn(&'a mut Client, &M) -> impl futures::Future`?

I'm very intersted in learning what's happening here.

I really researched about lifetimes some months ago, I tried finding everything I could, but they're still confusing to me

send_and_expect has an elided lifetime, you need to specify it when calling.

    pub fn connection_retrier<'a, 'b, T>(
        f: fn(&'a mut Self, &'b M) -> T,
... where
        T: Future<Output = std::result::Result<(), ()>> + 'a + 'b

The error message is rather bad to be honest. There’s two types in this error message:

for<'r> fn(&mut Client, &'r M) -> impl futures::Future

and

for<'a, '_> fn(&'a mut Client, &M) -> impl futures::Future

Both types are actually syntactically mis-represented. The second one is not even valid rust syntax, and the first one is not saying what it’s supposed to say. Let’s talk about lifetimes.

So, the for<…> thing related to higher-ranked lifetime bounds. In this case it’s actually about function types but they use the same syntax. A function type such as for<'a> fn(&'a mut Option<Foo>) -> Option<&'a mut Foo> is callable with references of any lifetime 'a and in this case, the return-type also depends on the choice of that lifetime. In terms of Fn-traits, this function pointer type satisfies the for<'a> Fn(&'a mut Option<Foo>) -> Option<&'a mut Foo> bound. Now, for function types, there’s actually a shorthand: Elided lifetimes in function pointer types and Fn-traits are standing for higher-ranked lifetimes:

In your connection_retrier, the f argument has type

fn(&'a mut Self, &M) -> T

This elided lifetime on &M translates to

for<'r> fn(&'a mut Self, &'r M) -> T

The function

async fn send_and_expect<'a>(&'a mut self, m: &M) -> std::result::Result<(), ()>

has an elided lifetime for the &M, so it’s equivalent to

async fn send_and_expect<'a, 'b>(&'a mut self, m: &'b M) -> std::result::Result<(), ()>

and the async stands for something like

fn send_and_expect<'a, 'b>(&'a mut self, m: &'b M) -> {some Future depending on 'a and 'b}

Now, usually the lifetime '_, also called “elided lifetime” is the same as writing no lifetime on references, in other words: &'_ M and &M mean the same. So lets talk about the weird types in the error messages:

for<'r> fn(&mut Client, &'r M) -> impl futures::Future
// and
for<'a, '_> fn(&'a mut Client, &M) -> impl futures::Future

They become less confusing (but not less wrong) if you write '_ for the elided lifetimes:

for<'r> fn(&'_ mut Client, &'r M) -> impl futures::Future
// and
for<'a, '_> fn(&'a mut Client, &'_ M) -> impl futures::Future

The problem is that something like

for<'r> fn(&'_ mut Client, &'r M) -> impl futures::Future

still stands for something like

 for<'r> for<'a> fn(&'a mut Client, &'r M) -> impl futures::Future

but this is not what the compiler means. AFAICT, it just uses '_ like some concrete lifetime name in this error message. This is most clear by the fact that it writes for<'a, '_> which is invalid syntax. So let’s fix the error message’s types by choosing some actual legal lifetime names instead of '_:

for<'r> fn(&'e mut Client, &'r M) -> impl futures::Future
// and
for<'a, 'e> fn(&'a mut Client, &'e M) -> impl futures::Future

Now we can understand how the message relates to your code: The type

for<'r> fn(&'e mut Client, &'r M) -> impl futures::Future

corresponds to the type of the argument f of connection_retrier, the lifetime 'e is supposed to be the parameter 'a of the function.

The type

for<'a, 'e> fn(&'a mut Client, &'e M) -> impl futures::Future

corresponds to the function pointer type of the function send_and_expect.


Now, initially it seems strange that

for<'a, 'e> fn(&'a mut Client, &'e M) -> impl futures::Future

can’t be converted into

for<'r> fn(&'e mut Client, &'r M) -> impl futures::Future

since the former actually should be more general than the latter; not the other way around. The main problem is, AFAICT, something different that isn’t apparent from the error message at all. The problem I see is that send_and_expect has a type

for<'a, 'e> fn(&'a mut Client, &'e M) -> {future depending on 'a and 'e}

which can (I think) easily be converted/used as the type

for<'r> fn(&'a mut Client, &'r M) -> {future depending on 'a and 'r}

On the other hand, the argument f of type

for<'r> fn(&'a mut Self, &'r M) -> T

is supposed to return a future type T that might contain 'a (since that’s a lifetime parameter of the function) but it cannot depend on 'r!

The error message just writes impl Future for both things, regardless of what lifetimes they depend on, and hence hides the actual problem here.

Okay, now onto ways of fixing your error. Assuming that you only want to apply f to f_m in your connection_retrier, the higher-ranked type is entirely unnecessary. You can just write f: fn(&'a mut Self, &'a M) -> T.

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.