What is the difference in lifetimes here between a closure, fn, and async fn?

I'm trying to implement a trait for async functions taking a reference. The compiler is happy when the async function is a closure, or a normal function with an async block, but won't accept the async fn declaration, with a mismatch in lifetimes; what's the difference here?

Playground: Rust Playground

Code:

use futures::Future;
use std::pin::Pin;

type ResponseFuture = dyn Future<Output = i32> + Send;
struct Foo;

trait Handler: Sync + Send {
    fn handle(&self, context: &Foo) -> Pin<Box<ResponseFuture>>;
}

impl<F, U> Handler for F
where
    F: Fn(&Foo) -> U + Send + Sync + 'static,
    U: Future<Output = i32> + Send + 'static,
{
    fn handle(&self, context: &Foo) -> Pin<Box<ResponseFuture>> {
        Box::pin(self(context))
    }
}

fn handle_root(ctx: &Foo) -> impl Future<Output=i32> {
    async { 0 }
}

async fn handle_root2(ctx: &Foo) -> i32 {
    0
}

fn main() {
    let h: Box<dyn Handler> = Box::new(|ctx: &Foo| async { 0 });
    let g: Box<dyn Handler> = Box::new(handle_root);
    let f: Box<dyn Handler> = Box::new(handle_root2);
}

Error:

error[E0271]: type mismatch resolving `for<'r> <for<'_> fn(&Foo) -> impl core::future::future::Future {handle_root2} as std::ops::FnOnce<(&'r Foo,)>>::Output == _`
  --> src/main.rs:32:31
   |
32 |     let f: Box<dyn Handler> = Box::new(handle_root2);
   |                               ^^^^^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime
   |
   = note: required because of the requirements on the impl of `Handler` for `for<'_> fn(&Foo) -> impl core::future::future::Future {handle_root2}`
   = note: required for the cast to the object type `dyn Handler`

In the type it gives, for<'r> <for<'_> fn(&Foo) -> impl core::future::future::Future {handle_root2}, it seems what it wants is something like for<'r> <for<'a> fn(&'a Foo) -> impl core::future::future::Future {handle_root2} - that is, for the parameter's lifetime to be bounded by the higher-rank trait bound?

The difference is that the future returned by the async fn stores the reference given as an argument, whereas the other two do not store it. This means that only the async fn need the lifetime of that argument annotated on the future.

1 Like

I see, thank you. But even if I could annotate the right type, it's rather pointless anyways since as soon as I try to use the reference, I run into other issues - which makes sense (that reference is not going to stay valid for very long).

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.