-
Notifications
You must be signed in to change notification settings - Fork 29
Async Closures With Borrowed Arguments #19
Comments
Just to tease out the different threads of this, let me know if anything about this isn't correct:
Am I misunderstanding any of this? It seems like we should solve 1 promptly (what blockers are there?) and 2 less promptly (since it involves design work). Pursuing the very complicated trait wrapper thing too far seems like a misfocus to me. |
Nevermind, I think I have a better understanding of the issue from investigating this comment:
The problem seems to be mainly that you can't write |
Is there any reason fully expanding the lifetime and closure return type out to generic type parameters doesn't work? fn higher_order_fn<'a, T, F>(async_fn: F)
where
T: Future<Output = ()> + 'a,
F: FnOnce(&'a Foo) -> T, This is how I was previously assuming higher order anonymous universal types would be desugared. |
@Nemo157 That gives you a function which can borrow a particular |
Coming from this URLO thread. I propose this workaround: #![feature(async_await)]
use std::future::Future;
macro_rules! impl_async_fn {
($(($FnOnce:ident, $FnMut:ident, $Fn:ident, ($($arg:ident: $arg_ty:ident,)*)),)*) => {
$(
pub trait $FnOnce<$($arg_ty,)*> {
type Output;
type Future: Future<Output = Self::Output> + Send;
fn call_once(self, $($arg: $arg_ty,)*) -> Self::Future;
}
pub trait $FnMut<$($arg_ty,)*>: $FnOnce<$($arg_ty,)*> {
fn call_mut(&mut self, $($arg: $arg_ty,)*) -> Self::Future;
}
pub trait $Fn<$($arg_ty,)*>: $FnMut<$($arg_ty,)*> {
fn call(&self, $($arg: $arg_ty,)*) -> Self::Future;
}
impl<$($arg_ty,)* F, Fut> $FnOnce<$($arg_ty,)*> for F
where
F: FnOnce($($arg_ty,)*) -> Fut,
Fut: Future + Send,
{
type Output = Fut::Output;
type Future = Fut;
fn call_once(self, $($arg: $arg_ty,)*) -> Self::Future {
self($($arg,)*)
}
}
impl<$($arg_ty,)* F, Fut> $FnMut<$($arg_ty,)*> for F
where
F: FnMut($($arg_ty,)*) -> Fut,
Fut: Future + Send,
{
fn call_mut(&mut self, $($arg: $arg_ty,)*) -> Self::Future {
self($($arg,)*)
}
}
impl<$($arg_ty,)* F, Fut> $Fn<$($arg_ty,)*> for F
where
F: Fn($($arg_ty,)*) -> Fut,
Fut: Future + Send,
{
fn call(&self, $($arg: $arg_ty,)*) -> Self::Future {
self($($arg,)*)
}
}
)*
}
}
impl_async_fn! {
(AsyncFnOnce0, AsyncFnMut0, AsyncFn0, ()),
(AsyncFnOnce1, AsyncFnMut1, AsyncFn1, (a0:A0, )),
(AsyncFnOnce2, AsyncFnMut2, AsyncFn2, (a0:A0, a1:A1, )),
(AsyncFnOnce3, AsyncFnMut3, AsyncFn3, (a0:A0, a1:A1, a2:A2, )),
(AsyncFnOnce4, AsyncFnMut4, AsyncFn4, (a0:A0, a1:A1, a2:A2, a3:A3, )),
(AsyncFnOnce5, AsyncFnMut5, AsyncFn5, (a0:A0, a1:A1, a2:A2, a3:A3, a4:A4, )),
(AsyncFnOnce6, AsyncFnMut6, AsyncFn6, (a0:A0, a1:A1, a2:A2, a3:A3, a4:A4, a5:A5, )),
(AsyncFnOnce7, AsyncFnMut7, AsyncFn7, (a0:A0, a1:A1, a2:A2, a3:A3, a4:A4, a5:A5, a6:A6, )),
(AsyncFnOnce8, AsyncFnMut8, AsyncFn8, (a0:A0, a1:A1, a2:A2, a3:A3, a4:A4, a5:A5, a6:A6, a7:A7, )),
(AsyncFnOnce9, AsyncFnMut9, AsyncFn9, (a0:A0, a1:A1, a2:A2, a3:A3, a4:A4, a5:A5, a6:A6, a7:A7, a8:A8, )),
(AsyncFnOnce10, AsyncFnMut10, AsyncFn10, (a0:A0, a1:A1, a2:A2, a3:A3, a4:A4, a5:A5, a6:A6, a7:A7, a8:A8, a9:A9, )),
(AsyncFnOnce11, AsyncFnMut11, AsyncFn11, (a0:A0, a1:A1, a2:A2, a3:A3, a4:A4, a5:A5, a6:A6, a7:A7, a8:A8, a9:A9, a10:A10, )),
(AsyncFnOnce12, AsyncFnMut12, AsyncFn12, (a0:A0, a1:A1, a2:A2, a3:A3, a4:A4, a5:A5, a6:A6, a7:A7, a8:A8, a9:A9, a10:A10, a11:A11, )),
}
pub async fn call<F>(f: F) -> i32
where
F: for<'a> AsyncFn1<&'a i32, Output = i32>,
{
f.call_once(&42).await
}
// #[runtime::test]
async fn test_call() {
async fn double(x: &i32) -> i32 { *x * 2 }
assert_eq!(call(double).await, 84);
} The pub trait AsyncFnOnce<A>: FnOnce<A, Output = Self::Future> {
type Output;
type Future: Future<Output = Self::Output>;
}
impl<F, A> AsyncFnOnce<A> for F
where
F: FnOnce<A>,
<F as FnOnce<A>>::Output: Future,
{
type Output = <<F as FnOnce<A>>::Output as Future>::Output,
type Future = <F as FnOnce<A>>::Output;
} But this is rejected due to recursive trait inheritance. Instead, we can say The callee has to write |
Async closures with borrowed arguments are a messy thing. Since the output type of the closure (
impl Future + 'a
) depends upon the input lifetime of the argument, these closures are always higher-ranked. Eventually I think we'd like to support this through syntax like this:Unfortunately, the current
impl Trait
-in-args implementation doesn't support these sorts of higher-ranked use cases. Luckily it's not backwards incompatible to add support for them, since we've currently bannedimpl Trait
inside ofFn
syntax.Until we get one of these nicer solutions, however, we need a way to type these sorts of functions. I made an attempt at one of them that looks like this:
Unfortunately, this fails because of rust-lang/rust#51004. Even if this worked, though, this is an awful lot of code to write for each of these functions-- we'd likely want some sort of macro.
Until progress is made on this issue, it's impossible to make a function which accepts an asynchronous closure with an argument borrowed for a non-fixed lifetime (precise named lifetimes like
impl FnOnce(&'tcx Foo) -> F
are possible).The text was updated successfully, but these errors were encountered: