A function called wrap
should take an impl FnOnce -> Fut
and return an impl FnOnce -> Ret
where Fut
and Ret
are Future
s.
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::pin
ning 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!