Async methods as parameters to another async method

Hi, I recently tried to play with async rust which is totally new to me.
I want to pass async methods (first and second) as parameters to another async method (call_on_list) and I get this error:

error[E0308]: mismatched types
  --> src/main.rs:34:5
   |
34 |     foo.call_on_list(Foo::first, list.clone()).await;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other

I've already tried with different lifetimes without success.
So my question is this:

  • Is there a syntax for doing this? And if so, which one?

Here is my code:

#[derive(Default)]
struct Foo {
    data: Vec<String>,
}

impl Foo {
    async fn first(&mut self, input: &str) {
        // do some async stuff
        // ...
        self.data.push(input.to_string());
    }
    async fn second(&mut self, input: &str) {
        // do some other async stuff
        // ...
        self.data.push(input.to_string());
    }
    
    // fix this method to accept incoming async method
    async fn call_on_list<F, Fut>(&mut self, mut f: F, list: Vec<&str>)
    where
        F: FnMut(&mut Foo, &str) -> Fut,
        Fut: std::future::Future<Output = ()>,
    {
        for item in list {
            f(self, item).await;
        }
    }
}

#[tokio::main]
async fn main() {
    let mut foo = Foo::default();
    let list = vec!["1", "2", "3"];
    foo.call_on_list(Foo::first, list.clone()).await;
    foo.call_on_list(Foo::second, list).await;
}

(Playground)

This:

    async fn first(&mut self, input: &str) { ... }

Is, behind the scenes, doing something like this:

type FirstFut<'a, 'b> = impl Future<Output = ()>;
fn first<'a, 'b>(&'a mut self, input: &'b str) -> FirstFut<'a, 'b> {
    async move {
        let _force_always_move = (&self, &input);
        // ...
    }
}

Of particular note, the opaque future type which is returned is parameterized by lifetimes. But here:

async fn call_on_list<F, Fut>(&mut self, mut f: F, list: Vec<&str>)
    where
        F: FnMut(&mut Foo, &str) -> Fut,
        Fut: std::future::Future<Output = ()>,

Fut has to be a singular, fully qualified type -- not something with free lifetime parameters, say. By implication, Fut can't capture (be parameterized by) any of the input lifetimes.

So it's impossible for first to meet that bound.

You can:

  • Hide the opaque types by using Pin<Box<dyn Future<...> + Send + 'a>> or so
  • Make your own AsyncFn trait that side-steps mentioning the return type in bounds

Here's an example of the former. There's other threads where I walk through the latter approach, and can do so again when I have some time if you like.[1]


  1. Really need to get around to writing up some more permanent pages on Fn and friends... ↩ī¸Ž

I ended up with a reason to do this, so here you go.

Here's a version that demonstrates the general technique. It's good enough for your OP, but as the comments indicate, it won't work for non-'static-satisfying types. (Foo: 'static so it works for your OP.)

This one works with non-'static types too.

Feel free to ask if you have questions.

1 Like

Thank you very much for your help.
It works perfectly :+1:

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.