Wrap an FnOnce -> Future in an FnOnce -> Future

A function called wrap should take an impl FnOnce -> Fut and return an impl FnOnce -> Ret where Fut and Ret are Futures.

fn wrap<Fut, Ret>(f: impl FnOnce() -> Fut) -> impl FnOnce() -> Ret
where
    Fut: std::future::Future,
    Ret: std::future::Future,
{
    || async {
        println!("Start");
        f().await;
        println!("Done");
    }
}
It does not compile.
error[E0308]: mismatched types
  --> src/main.rs:6:8
   |
1  |   fn wrap<Fut, Ret>(f: impl FnOnce() -> Fut) -> impl FnOnce() -> Ret
   |                --- expected this type parameter
...
6  |       || async {
   |  ________^
7  | |         println!("Start");
8  | |         f().await;
9  | |         println!("Done");
10 | |     }
   | |_____^ expected type parameter `Ret`, found `async` block
   |
   = note: expected type parameter `Ret`
               found `async` block `{async block@src/main.rs:6:8: 10:6}`
   = help: every closure has a distinct type and so could not always match the caller-chosen type of parameter `Ret`

I also tried this formulation:

fn wrap2<Fut, Ret>(f: impl FnOnce() -> Fut) -> impl FnOnce() -> impl std::future::Future
where
    Fut: std::future::Future,
{
    || async {
        println!("Start");
        f().await;
        println!("Done");
    }
}
It does not compile either.
error[E0562]: `impl Trait` is not allowed in the return type of `Fn` trait bounds
 --> src/main.rs:1:64
  |
1 | fn wrap<Fut, Ret>(f: impl FnOnce() -> Fut) -> impl FnOnce() -> impl std::future::Future
  |                                                                ^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `impl Trait` is only allowed in arguments and return types of functions and methods
  = note: see issue #99697 <https://github.com/rust-lang/rust/issues/99697> for more information

Is it fundamentally impossible to achieve the goal illustrated by the examples above?

fn wrap<'a, Fut>(f: impl FnOnce() -> Fut + 'a) 
    -> impl FnOnce() 
    -> Box<dyn std::future::Future<Output = ()> + 'a>
where
    Fut: std::future::Future,
{
    || Box::new(async {
        println!("Start");
        f().await;
        println!("Done");
    })
}

Closures aren't able to have impl trait in return position (not yet...). Instead you can return a closure that returns a trait object. If you're working a lot with Futures stuff, you may want to consider Box::pinning the Future as well.

Your first formulation is incorrect as the caller of the function is allowed to choose Ret, not your function, so they might choose a different type that what your function returns.

The second formulation is logically correct, but unfortunately doesn't work due to a limitation of the language.

Not much reason to not Box::pin instead of Box::new, I'd think? You can't move out of the box or anything.

Apologies, I still had poll on my mind and though it would be worth adding if they were also working with the Future trait directly.

Had a play with this!

You can use the mentioned unstable impl_trait_in_fn_trait_return to get this working without the heap allocation on nightly Rust:

#![feature(impl_trait_in_fn_trait_return)]

use std::future::Future;

fn foo() -> impl FnOnce() -> impl Future<Output = impl FnOnce() -> impl Future<Output = u32>> {
    || async {
        || async {
            42
        }
    }
}

async fn use_foo() {
    assert_eq!((foo()().await)().await, 42);
}

Or you can make your own async fn trait to make this a little prettier, and it even works on stable!

use std::future::Future;

trait AsyncFnOnce0 {
    type Output;

    async fn async_call(self) -> Self::Output;
}

impl<F, Fut> AsyncFnOnce0 for F
where F: FnOnce() -> Fut,
      Fut: Future,
{
    type Output = Fut::Output;
    
    async fn async_call(self) -> Self::Output {
        (self)().await
    }
}

fn bar() -> impl AsyncFnOnce0<Output = impl AsyncFnOnce0<Output = u32>> {
    || async {
        || async {
            42
        }
    }
}

async fn use_bar() {
    assert_eq!(bar().async_call().await.async_call().await, 42);
}

The trade-off is you have to repeat that trait for every argument arity (e.g. 0, 1, 2, ... arguments) and self type (e.g. &self for Fn, &mut self for FnMut, self for FnOnce) - a bit lame.

1 Like

I tried using the unstable feature you proposed but it is very much broken. None of these functions compiles:

#![feature(impl_trait_in_fn_trait_return)]

use std::future::Future;

fn wrap_v1<Fut>(f: Fut) -> impl FnOnce() -> impl Future<Output = ()> {
    || async {}
}

fn wrap_v2(f: impl FnOnce()) -> impl FnOnce() -> impl Future<Output = ()> {
    || async {}
}

The approach with the custom async fn trait works indeed. Adapted to the original post:

use std::future::Future;

trait AsyncFnOnce0 {
    type Output;

    async fn async_call(self) -> Self::Output;
}

impl<F, Fut> AsyncFnOnce0 for F
where
    F: FnOnce() -> Fut,
    Fut: Future,
{
    type Output = Fut::Output;

    async fn async_call(self) -> Self::Output {
        self().await
    }
}

fn wrap(f: impl AsyncFnOnce0<Output = ()>) -> impl AsyncFnOnce0<Output = ()> {
    || async {
        println!("Start");
        let fut = f.async_call();
        fut.await;
        println!("Done");
    }
}

Thanks to everyone! :slight_smile: