How to Accept an Async Function as an Argument?

I would like to replicate the behavior and ergonomics of taking a closure/function as an argument much like map does: iterator.map(|x| ...).

I've noticed that some library code allows passing in async functionality, but this method doesn't allow me to pass in arguments:

pub fn spawn<F, T>(future: F) -> JoinHandle<T>
where
    F: Future<Output = T> + Send + 'static,
    T: Send + 'static,
spawn(async { foo().await });

I'm hoping to do one of the following:

iterator.map(async |x| {...});
async fn a(x: _) {}
iterator.map(a)
3

3 Answers

async functions are effectively desugared as returning impl Future. Once you know that, it's a matter of combining existing Rust techniques to accept a function / closure, resulting in a function with two generic types:

use std::future::Future;

async fn example<F, Fut>(f: F)
where
    F: FnOnce(i32, i32) -> Fut,
    Fut: Future<Output = bool>,
{
    f(1, 2).await;
}

This can also be written as

use std::future::Future;

async fn example<Fut>(f: impl FnOnce(i32, i32) -> Fut)
where
    Fut: Future<Output = bool>,
{
    f(1, 2).await;
}
2

The async |...| expr closure syntax is available on the nightly channel enabling the feature async_closure.

#![feature(async_closure)]

use futures::future;
use futures::Future;
use tokio;

pub struct Bar;

impl Bar {
    pub fn map<F, T>(&self, f: F)
    where
        F: Fn(i32) -> T,
        T: Future<Output = Result<i32, i32>> + Send + 'static,
    {
        tokio::spawn(f(1));
    }
}

async fn foo(x: i32) -> Result<i32, i32> {
    println!("running foo");
    future::ok::<i32, i32>(x).await
}

#[tokio::main]
async fn main() {
    let bar = Bar;
    let x = 1;

    bar.map(foo);

    bar.map(async move |x| {
        println!("hello from async closure.");
        future::ok::<i32, i32>(x).await
    });
}

See the 2394-async_await RFC for more detalis

It's too late but I hope this code helps others. In my situation I have some predefined functions which I can not change them (func0, func1, ...) and I need to run them all till the last one in order. If one fails, the whole operation must terminate. So I created an array of functions, then each item will get executed during a loop.

use std::future::Future;
use std::pin::Pin;    

type FuncResult = dyn Future<Output=Result<(), String>>;

async fn apply_funcs(mut start: usize, end: usize) {
   let funcs = [func0];
   let mut result = true;
   while start < end && result {
      result = func_wrapper(funcs[start]).await;
      start += 1;
   }
}

async fn func_wrapper(f: impl FnOnce() -> FuncResult) -> bool {
   match f().await {
      Ok(_) => { println!("ok"); }
      Err(e) => {
         println!("{e}");
         return false;
      }
   }
   true
}

async fn func0() -> Result<(), String> {
   todo!()
}

This code fails: expected dyn Future, found future

I found 2 ways to solve that:

1: By static dispatching which is by generics:

async fn apply_funcs(mut start: usize, end: usize) {
   let funcs = [func0];
   let mut result = true;
   while start < end && result {
      result = func_wrapper(funcs[start]).await;
      start += 1;
   }
}

async fn func_wrapper<Fut>(f: impl FnOnce() -> Fut) -> bool
   where Fut: Future<Output=Result<(), String>> {
   match f().await {
      Ok(_) => { println!("ok"); }
      Err(e) => {
         println!("{e}");
         return false;
      }
   }
   true
}

async fn func0() -> Result<(), String> {
   todo!()
}

2: By dynamic dispatching via defining another closure:

type FuncResult = Pin<Box<dyn Future<Output=Result<(), String>>>>;

async fn apply_funcs(mut start: usize, end: usize) {
   let funcs = [
      || -> FuncResult {
         Box::pin(func0())// no await here
      }
   ];
   let mut result = true;
   while start < end && result {
      result = func_wrapper(funcs[start]).await;
      start += 1;
   }
}

async fn func_wrapper(f: impl FnOnce() -> FuncResult) -> bool {
   match f().await {
      Ok(_) => { println!("ok"); }
      Err(e) => {
         println!("{e}");
         return false;
      }
   }
   true
}

async fn func0() -> Result<(), String> {
   todo!()
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Robert Thorne

Robert Thorne

Automotive & Future Transportation Editor

Robert Thorne covers electric vehicle innovations, autonomous driving systems, global mobility trends, and automotive engineering developments.

Share this article
Twitter Facebook Pinterest