Christian
Christian

Reputation: 942

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)

Upvotes: 47

Views: 28743

Answers (4)

cafce25
cafce25

Reputation: 27357

You can use

the trait bound for async closures async Fn[Once/Mut](Args) -> ReturnType (although this may be changed):

#![feature(async_closure)]

async fn my_func<F>(closure: F)
where
    F: async Fn(),
{
    closure().await;
    closure().await;
    closure().await;
}

This translates into a bound on AsyncFn[Once/Mut], but you can't spell these traits directly.

As explained by Chayim in this duplicate

Upvotes: 0

A R
A R

Reputation: 11

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!()
}

Upvotes: 1

Shepmaster
Shepmaster

Reputation: 430711

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;
}

Upvotes: 75

attdona
attdona

Reputation: 18943

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

Upvotes: 9

Related Questions