I was almost able to stuff it through a trait AsyncFn
shaped hole, but it's quite unpretty and unfortunately both absolutely hates type inference and fails to satisfy lifetime constraints because of the use of a custom trait, even before trying to do anything interesting. [playground]
Trying to force it to "work"
pub trait AsyncFn<Args>: FnOnce(Args) -> Self::ImplFuture {
type Output;
type ImplFuture: Future<Output = <Self as AsyncFn<Args>>::Output>;
fn into_future(self, args: Args) -> Self::ImplFuture;
}
impl<Fn, Args, Ret> AsyncFn<Args> for Fn
where
Fn: FnOnce(Args) -> Ret,
Ret: Future,
{
type Output = Ret::Output;
type ImplFuture = Ret;
fn into_future(self, args: Args) -> Ret {
self(args)
}
}
pub async fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> AsyncFn<&'scope Scope<'scope, 'env>, Output = T>,
{ /* .. */ }
fn task<'scope>(s: &'scope Scope<'scope, '_>) -> impl Future<Output = ()> + 'scope {
async move {
s.spawn(async {});
}
}
fn main() {
// okay
scope(task);
// error
scope(|s: &Scope| {
s.spawn(async move {});
async {}
});
}
error: lifetime may not live long enough
--> src/main.rs:61:9
|
60 | scope(|s: &Scope| {
| - - let's call the lifetime of this reference `'1`
| |
| has type `&Scope<'2, '_>`
61 | s.spawn(async move {});
| ^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'2`
This isn't directly resolvable without inline for<'a>
closure lifetime binder syntax to force the closure to use the same lifetime for 'scope
. Alternatively, I could relax Scope::spawn
to take &self
instead of &'scope self
(I don't think that restriction is relevant to soundness), and then things start to look like they're working, but it's still a hack that runs into lifetime issues for the closure when trying to actually capture the scope parameter into an async
block, e.g.
fn task<'scope>(s: &'scope Scope<'scope, '_>) -> impl Future<Output = ()> + 'scope {
async move {
s.spawn(async {});
}
}
fn main() {
// this is okay
scope(task);
// but this is an error
scope(|s: &Scope| async move {
s.spawn(async {});
});
}
error: lifetime may not live long enough
--> src/main.rs:61:23
|
61 | scope(|s: &Scope| async move {
| _______________-_____-_^
| | | |
| | | return type of closure `[async block@src/main.rs:61:23: 63:6]` contains a lifetime `'2`
| | let's call the lifetime of this reference `'1`
62 | | s.spawn(async {});
63 | | });
| |_____^ returning this value requires that `'1` must outlive `'2`
error: lifetime may not live long enough
--> src/main.rs:61:23
|
61 | scope(|s: &Scope| async move {
| ____________-________-_^
| | | |
| | | return type of closure `[async block@src/main.rs:61:23: 63:6]` contains a lifetime `'4`
| | has type `&Scope<'3, '_>`
62 | | s.spawn(async {});
63 | | });
| |_____^ returning this value requires that `'3` must outlive `'4`
|
= note: requirement occurs because of the type `Scope<'_, '_>`, which makes the generic argument `'_` invariant
= note: the struct `Scope<'scope, 'env>` is invariant over the parameter `'scope`
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
error: lifetime may not live long enough
--> src/main.rs:61:23
|
61 | scope(|s: &Scope| async move {
| ____________-________-_^
| | | |
| | | return type of closure `[async block@src/main.rs:61:23: 63:6]` contains a lifetime `'2`
| | has type `&Scope<'_, '5>`
62 | | s.spawn(async {});
63 | | });
| |_____^ returning this value requires that `'5` must outlive `'2`
Unstably using FnOnce
directly also almost works, but also runs into a weird error, likely due to the Fn*
traits not really being intended to be used to project to their Output
associated type. [playground]
Attempted crimes against stability
pub async fn scope<'env, F, T>(f: F) -> T
where
F: for<'scope> FnOnce<(&'scope Scope<'scope, 'env>,)>,
for<'scope> <F as FnOnce<(&'scope Scope<'scope, 'env>,)>>::Output: Future<Output = T>,
{ /* .. */ }
async fn task<'scope>(_: &'scope Scope<'scope, '_>) {}
fn main() {
scope(task);
}
error[E0277]: `<_ as FnOnce<(&'scope Scope<'scope, '_>,)>>::Output` is not a future
--> src/main.rs:38:11
|
38 | scope(task);
| ----- ^^^^ `<_ as FnOnce<(&'scope Scope<'scope, '_>,)>>::Output` is not a future
| |
| required by a bound introduced by this call
|
= help: the trait `for<'scope> Future` is not implemented for `<_ as FnOnce<(&'scope Scope<'scope, '_>,)>>::Output`
= note: <_ as FnOnce<(&'scope Scope<'scope, '_>,)>>::Output must be a future or must implement `IntoFuture` to be awaited
note: required by a bound in `scope`
--> src/main.rs:29:72
|
26 | pub async fn scope<'env, F, T>(f: F) -> T
| ----- required by a bound in this function
...
29 | for<'scope> <F as FnOnce<(&'scope Scope<'scope, 'env>,)>>::Output: Future<Output = T>,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `scope`