How to pass async function which accepts parameter with lifetime to other function?

Hello,

I'm struggling to pass async function/closure which accepts a parameter with a lifetime to other async function as a parameter. Please review a code snippet below. How to pass callback to listen?

use std::future::Future;

pub struct Response<'a> {
    body: &'a mut [u8],
}

impl<'a> Response<'a> {
    pub fn new(body: &'a mut [u8]) -> Self {
        Self { body }
    }
    
    pub fn send(&self) {}
}

pub struct Server {}

impl Server {
    pub async fn listen<F, T>(&self, cb: F)
    where
        F: Fn(Response) -> T,
        T: Future<Output = ()>,
    {
        let mut buf = [0_u8, 8];
        let response = Response::new(&mut buf);
        cb(response).await;
    }
}

async fn callback(res: Response<'_>) {
    res.send();
}

#[async_std::main]
async fn main() {
    let server = Server{};
    server.listen(callback).await;
}

This code doesn't compile with mismatched types error:

error[E0308]: mismatched types
  --> src/main.rs:35:5
   |
35 |     server.listen(callback);
   |     ^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected opaque type `impl for<'a> Future<Output = ()>`
              found opaque type `impl Future<Output = ()>`
   = help: consider `await`ing on both `Future`s
   = note: distinct uses of `impl Trait` result in different opaque types
note: the lifetime requirement is introduced here
  --> src/main.rs:20:28
   |
20 |         F: Fn(Response) -> T,
   |                            ^

If I pass a closure with async body instead then I get another error:

#[async_std::main]
async fn main() {
    let server = Server{};
    server.listen(|res| async {
        res.send();
    });
}
error: lifetime may not live long enough
  --> src/main.rs:40:25
   |
40 |       server.listen(|res| async {
   |  ____________________----_^
   | |                    |  |
   | |                    |  return type of closure `[async block@src/main.rs:40:25: 42:6]` contains a lifetime `'2`
   | |                    has type `Response<'1>`
41 | |         res.send();
42 | |     });
   | |_____^ returning this value requires that `'1` must outlive `'2`

error[E0373]: async block may outlive the current function, but it borrows `res`, which is owned by the current function
  --> src/main.rs:40:25
   |
40 |       server.listen(|res| async {
   |  _________________________^
41 | |         res.send();
   | |         --- `res` is borrowed here
42 | |     });
   | |_____^ may outlive borrowed value `res`
   |
note: async block is returned here
  --> src/main.rs:40:25
   |
40 |       server.listen(|res| async {
   |  _________________________^
41 | |         res.send();
42 | |     });
   | |_____^

Playground without async_std but the error is the same:

I understand that I should set up the lifetimes but I don't understand how to do this in terms of syntax. How can I specify the lifetimes for such case?

You just have to let the compiler know the explicit lifetimes of your function calls:

impl Server {
    pub async fn listen<'a, 'b:'a, F, T>(&'b self, cb: F)
    where
        F: Fn(Response<'a>) -> T,
        T: Future<Output = ()>,
    {
        let mut buf = [0_u8, 8];
        let response = Response::new(&mut buf);
        cb(response).await;
    }
}

Once you put the code above in your playground, you'll notice that the compiler will complain about another issue next:

23 |         let mut buf = [0_u8, 8];
   |             ------- binding `buf` declared here
24 |         let response = Response::new(&mut buf);
   |                                      ^^^^^^^^ borrowed value does not live long enough
25 |         cb(response).await;
   |         ------------ argument requires that `buf` is borrowed for `'a`
26 |     }
   |     - `buf` dropped here while still borrowed

So your code cannot compile as-is.

The problem is that in the notionally desugared function signature below:

// async fn callback(res: Response<'_>) {
fn callback<'res>(res: Response<'res>) -> impl Future<Output = ()> + 'res {

The lifetime of the return type varies based on the input lifetime 'res (and types that differ in lifetime are distinct types). Whereas here:

    pub async fn listen<F, T>(&self, cb: F)
    where
        // F: Fn(Response) -> T,
        // // tip: Use #![deny(elided_lifetimes_in_paths)]
        // F: Fn(Response<'_>) -> T,
        // // tip: The above is sugar for...
        F: for<'any> Fn(Response<'any>) -> T,
        T: Future<Output = ()>,

T must resolve to a single type.

@moy2010's reply tried changing the bounds of F to match a single lifetime instead of any input lifetime (aka a HRTB or higher-ranked trait bound). This makes the bounds compatible with callback, but as it turns out, you do need the HRTB because of how you're trying to call it -- with the borrow of a local variable, which is a lifetime the caller cannot name much less provide.

So you need some way to write this HRTB combination:

F: for<'any> Fn(Response<'any>) -> ??,
for<'any> <F as Fn(Response<'any>)>::ReturnType: 'any + Future<Output = ()>>

// Or perhaps
F: for<'any> Fn(Response<'any>) -> impl 'any + Future<Output = ()>,

And unfortunately Rust has no way to directly write such bounds as of yet, as it forces you to mention the return type and doesn't support impl Trait in that bound position.

You may be able to work around this by implementing a wrapper trait with enough boilerplate:

// Off-the-cuff
trait Trait {
    type Out<'a>: Future<Output = ()>;
    fn call(&self, res: Response<'_>) -> Self::Out<'_>;
}

As with this trait, you don't have to mention the return type in the bound.

I have to run right now; I may return to this later if no one else demonstrates such an approach.

2 Likes
// A trait representing a callback that can handle a specific single lifetime.
//
// The supertrait implies that this implements `Fn` elsewhere.
//
// By using our own trait, we don't have to mention the return type or resolve
// it down to a single type (generic type variable)
pub trait ResponseCallbackBootstrap<'a>
where
    Self: Fn(Response<'a>) -> <Self as ResponseCallbackBootstrap<'a>>::FutBootstrap
{
    type FutBootstrap: Future<Output = ()>;
}

// Implement it for anything that matches the bounds
impl<'res, F, Fut> ResponseCallbackBootstrap<'res> for F
where
    F: Fn(Response<'res>) -> Fut,
    Fut: Future<Output = ()>,
{
    type FutBootstrap = Fut;
}
// A trait representing a callback that can handle any lifetime
// (for more ergonomic bounds elsewhere)
pub trait ResponseCallback: for<'any> ResponseCallbackBootstrap<'any> {}

// Implement it for anything that matches the bounds
impl<F> ResponseCallback for F
where
    for<'any> F: ResponseCallbackBootstrap<'any>
{}
impl Server {
    pub async fn listen<F: ResponseCallback>(&self, cb: F) {
    //                  ^^^^^^^^^^^^^^^^^^^
        let mut buf = [0_u8, 8];
        let response = Response::new(&mut buf);
        cb(response).await;
    }
}
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.