48

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
  • 1
    I think you need a Stream to achieve that, have a look at the crate futures Commented Mar 17, 2020 at 7:18
  • @MarioSantini docs.rs/async-std/1.5.0/async_std/stream/… It does not appear obvious that the functions they're accepting are async, rather the final result appears to be.
    – Christian
    Commented Mar 17, 2020 at 7:26
  • 1
    What I ment, was just: if you need to iterate asynchrounously on a collection, you may need a stream instead, that is a feature on which you can iterate. Commented Mar 17, 2020 at 8:51

4 Answers 4

76

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
  • I Dart if the .await is left off it results in -> Future<Output = Future<Output = bool>> it is flattened automatically to Future<Output = bool'>, so the .await is implicit. I haven't found anything so far if Rust does this as well. Commented Apr 7, 2023 at 15:17
  • 1
    @GünterZöchbauer not that I'm aware of. Such a thing being implicit feels a little too "loose" to me for Rust, so I'd be surprised if it were added.
    – Shepmaster
    Commented Apr 14, 2023 at 20:37
9

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

1

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

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

2
  • I reopened that question because while this OP probably wanted closure returning future and not async closure (it was highly experimental then), OP in the other question likely did intend to write an async closure. Linking between them is good, but I don't think it is worth an answer. Commented Jul 23, 2024 at 14:03
  • @ChayimFriedman people finding this question now probably benefit from an answer using contemporary solutions, having to follow a link is suboptimal, therefore this answer should be here.
    – cafce25
    Commented Jul 23, 2024 at 14:07

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.