Skip to content

Commit 8c6cf3c

Browse files
authored
Rollup merge of #119305 - compiler-errors:async-fn-traits, r=oli-obk
Add `AsyncFn` family of traits I'm proposing to add a new family of `async`hronous `Fn`-like traits to the standard library for experimentation purposes. ## Why do we need new traits? On the user side, it is useful to be able to express `AsyncFn` trait bounds natively via the parenthesized sugar syntax, i.e. `x: impl AsyncFn(&str) -> String` when experimenting with async-closure code. This also does not preclude `AsyncFn` becoming something else like a trait alias if a more fundamental desugaring (which can take many[^1] different[^2] forms) comes around. I think we should be able to play around with `AsyncFn` well before that, though. I'm also not proposing stabilization of these trait names any time soon (we may even want to instead express them via new syntax, like `async Fn() -> ..`), but I also don't think we need to introduce an obtuse bikeshedding name, since `AsyncFn` just makes sense. ## The lending problem: why not add a more fundamental primitive of `LendingFn`/`LendingFnMut`? Firstly, for `async` closures to be as flexible as possible, they must be allowed to return futures which borrow from the async closure's captures. This can be done by introducing `LendingFn`/`LendingFnMut` traits, or (equivalently) by adding a new generic associated type to `FnMut` which allows the return type to capture lifetimes from the `&mut self` argument of the trait. This was proposed in one of [Niko's blog posts](https://smallcultfollowing.com/babysteps/blog/2023/05/09/giving-lending-and-async-closures/). Upon further experimentation, for the purposes of closure type- and borrow-checking, I've come to the conclusion that it's significantly harder to teach the compiler how to handle *general* lending closures which may borrow from their captures. This is, because unlike `Fn`/`FnMut`, the `LendingFn`/`LendingFnMut` traits don't form a simple "inheritance" hierarchy whose top trait is `FnOnce`. ```mermaid flowchart LR Fn FnMut FnOnce LendingFn LendingFnMut Fn -- isa --> FnMut FnMut -- isa --> FnOnce LendingFn -- isa --> LendingFnMut Fn -- isa --> LendingFn FnMut -- isa --> LendingFnMut ``` For example: ``` fn main() { let s = String::from("hello, world"); let f = move || &s; let x = f(); // This borrows `f` for some lifetime `'1` and returns `&'1 String`. ``` That trait hierarchy means that in general for "lending" closures, like `f` above, there's not really a meaningful return type for `<typeof(f) as FnOnce>::Output` -- it can't return `&'static str`, for example. ### Special-casing this problem: By splitting out these traits manually, and making sure that each trait has its own associated future type, we side-step the issue of having to answer the questions of a general `LendingFn`/`LendingFnMut` implementation, since the compiler knows how to generate built-in implementations for first-class constructs like async closures, including the required future types for the (by-move) `AsyncFnOnce` and (by-ref) `AsyncFnMut`/`AsyncFn` trait implementations. [^1]: For example, with trait transformers, we may eventually be able to write: `trait AsyncFn = async Fn;` [^2]: For example, via the introduction of a more fundamental "`LendingFn`" trait, plus a [special desugaring with augmented trait aliases](https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/Lending.20closures.20and.20Fn*.28.29.20-.3E.20impl.20Trait/near/408471480).
2 parents 039d887 + 17b4333 commit 8c6cf3c

File tree

7 files changed

+157
-1
lines changed

7 files changed

+157
-1
lines changed

compiler/rustc_hir/src/lang_items.rs

+4
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ language_item_table! {
208208
FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
209209
FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
210210

211+
AsyncFn, sym::async_fn, async_fn_trait, Target::Trait, GenericRequirement::Exact(1);
212+
AsyncFnMut, sym::async_fn_mut, async_fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
213+
AsyncFnOnce, sym::async_fn_once, async_fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
214+
211215
FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None;
212216

213217
Iterator, sym::iterator, iterator_trait, Target::Trait, GenericRequirement::Exact(0);

compiler/rustc_hir_typeck/src/callee.rs

+11
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
220220
(self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true),
221221
(self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true),
222222
(self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false),
223+
(self.tcx.lang_items().async_fn_trait(), Ident::with_dummy_span(sym::async_call), true),
224+
(
225+
self.tcx.lang_items().async_fn_mut_trait(),
226+
Ident::with_dummy_span(sym::async_call_mut),
227+
true,
228+
),
229+
(
230+
self.tcx.lang_items().async_fn_once_trait(),
231+
Ident::with_dummy_span(sym::async_call_once),
232+
false,
233+
),
223234
] {
224235
let Some(trait_def_id) = opt_trait_def_id else { continue };
225236

compiler/rustc_span/src/symbol.rs

+6
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,14 @@ symbols! {
425425
assume,
426426
assume_init,
427427
async_await,
428+
async_call,
429+
async_call_mut,
430+
async_call_once,
428431
async_closure,
432+
async_fn,
429433
async_fn_in_trait,
434+
async_fn_mut,
435+
async_fn_once,
430436
async_fn_track_caller,
431437
async_for_loop,
432438
async_iterator,
+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use crate::future::Future;
2+
use crate::marker::Tuple;
3+
4+
/// An async-aware version of the [`Fn`](crate::ops::Fn) trait.
5+
///
6+
/// All `async fn` and functions returning futures implement this trait.
7+
#[unstable(feature = "async_fn_traits", issue = "none")]
8+
#[rustc_paren_sugar]
9+
#[fundamental]
10+
#[must_use = "async closures are lazy and do nothing unless called"]
11+
#[cfg_attr(not(bootstrap), lang = "async_fn")]
12+
pub trait AsyncFn<Args: Tuple>: AsyncFnMut<Args> {
13+
/// Future returned by [`AsyncFn::async_call`].
14+
#[unstable(feature = "async_fn_traits", issue = "none")]
15+
type CallFuture<'a>: Future<Output = Self::Output>
16+
where
17+
Self: 'a;
18+
19+
/// Call the [`AsyncFn`], returning a future which may borrow from the called closure.
20+
#[unstable(feature = "async_fn_traits", issue = "none")]
21+
extern "rust-call" fn async_call(&self, args: Args) -> Self::CallFuture<'_>;
22+
}
23+
24+
/// An async-aware version of the [`FnMut`](crate::ops::FnMut) trait.
25+
///
26+
/// All `async fn` and functions returning futures implement this trait.
27+
#[unstable(feature = "async_fn_traits", issue = "none")]
28+
#[rustc_paren_sugar]
29+
#[fundamental]
30+
#[must_use = "async closures are lazy and do nothing unless called"]
31+
#[cfg_attr(not(bootstrap), lang = "async_fn_mut")]
32+
pub trait AsyncFnMut<Args: Tuple>: AsyncFnOnce<Args> {
33+
/// Future returned by [`AsyncFnMut::async_call_mut`].
34+
#[unstable(feature = "async_fn_traits", issue = "none")]
35+
type CallMutFuture<'a>: Future<Output = Self::Output>
36+
where
37+
Self: 'a;
38+
39+
/// Call the [`AsyncFnMut`], returning a future which may borrow from the called closure.
40+
#[unstable(feature = "async_fn_traits", issue = "none")]
41+
extern "rust-call" fn async_call_mut(&mut self, args: Args) -> Self::CallMutFuture<'_>;
42+
}
43+
44+
/// An async-aware version of the [`FnOnce`](crate::ops::FnOnce) trait.
45+
///
46+
/// All `async fn` and functions returning futures implement this trait.
47+
#[unstable(feature = "async_fn_traits", issue = "none")]
48+
#[rustc_paren_sugar]
49+
#[fundamental]
50+
#[must_use = "async closures are lazy and do nothing unless called"]
51+
#[cfg_attr(not(bootstrap), lang = "async_fn_once")]
52+
pub trait AsyncFnOnce<Args: Tuple> {
53+
/// Future returned by [`AsyncFnOnce::async_call_once`].
54+
#[unstable(feature = "async_fn_traits", issue = "none")]
55+
type CallOnceFuture: Future<Output = Self::Output>;
56+
57+
/// Output type of the called closure's future.
58+
#[unstable(feature = "async_fn_traits", issue = "none")]
59+
type Output;
60+
61+
/// Call the [`AsyncFnOnce`], returning a future which may move out of the called closure.
62+
#[unstable(feature = "async_fn_traits", issue = "none")]
63+
extern "rust-call" fn async_call_once(self, args: Args) -> Self::CallOnceFuture;
64+
}
65+
66+
mod impls {
67+
use super::{AsyncFn, AsyncFnMut, AsyncFnOnce};
68+
use crate::future::Future;
69+
use crate::marker::Tuple;
70+
71+
#[unstable(feature = "async_fn_traits", issue = "none")]
72+
impl<F: Fn<A>, A: Tuple> AsyncFn<A> for F
73+
where
74+
<F as FnOnce<A>>::Output: Future,
75+
{
76+
type CallFuture<'a> = <F as FnOnce<A>>::Output where Self: 'a;
77+
78+
extern "rust-call" fn async_call(&self, args: A) -> Self::CallFuture<'_> {
79+
self.call(args)
80+
}
81+
}
82+
83+
#[unstable(feature = "async_fn_traits", issue = "none")]
84+
impl<F: FnMut<A>, A: Tuple> AsyncFnMut<A> for F
85+
where
86+
<F as FnOnce<A>>::Output: Future,
87+
{
88+
type CallMutFuture<'a> = <F as FnOnce<A>>::Output where Self: 'a;
89+
90+
extern "rust-call" fn async_call_mut(&mut self, args: A) -> Self::CallMutFuture<'_> {
91+
self.call_mut(args)
92+
}
93+
}
94+
95+
#[unstable(feature = "async_fn_traits", issue = "none")]
96+
impl<F: FnOnce<A>, A: Tuple> AsyncFnOnce<A> for F
97+
where
98+
<F as FnOnce<A>>::Output: Future,
99+
{
100+
type CallOnceFuture = <F as FnOnce<A>>::Output;
101+
102+
type Output = <<F as FnOnce<A>>::Output as Future>::Output;
103+
104+
extern "rust-call" fn async_call_once(self, args: A) -> Self::CallOnceFuture {
105+
self.call_once(args)
106+
}
107+
}
108+
}

library/core/src/ops/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
#![stable(feature = "rust1", since = "1.0.0")]
140140

141141
mod arith;
142+
mod async_function;
142143
mod bit;
143144
mod control_flow;
144145
mod coroutine;
@@ -173,6 +174,9 @@ pub use self::drop::Drop;
173174
#[stable(feature = "rust1", since = "1.0.0")]
174175
pub use self::function::{Fn, FnMut, FnOnce};
175176

177+
#[unstable(feature = "async_fn_traits", issue = "none")]
178+
pub use self::async_function::{AsyncFn, AsyncFnMut, AsyncFnOnce};
179+
176180
#[stable(feature = "rust1", since = "1.0.0")]
177181
pub use self::index::{Index, IndexMut};
178182

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// edition: 2021
2+
// check-pass
3+
4+
#![feature(async_fn_traits)]
5+
6+
use std::ops::AsyncFn;
7+
8+
async fn foo() {}
9+
10+
async fn call_asyncly(f: impl AsyncFn(i32) -> i32) -> i32 {
11+
f(1).await
12+
}
13+
14+
fn main() {
15+
let fut = call_asyncly(|x| async move { x + 1 });
16+
}

tests/ui/did_you_mean/bad-assoc-ty.stderr

+8-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,14 @@ error[E0223]: ambiguous associated type
191191
--> $DIR/bad-assoc-ty.rs:33:10
192192
|
193193
LL | type H = Fn(u8) -> (u8)::Output;
194-
| ^^^^^^^^^^^^^^^^^^^^^^ help: use fully-qualified syntax: `<(dyn Fn(u8) -> u8 + 'static) as IntoFuture>::Output`
194+
| ^^^^^^^^^^^^^^^^^^^^^^
195+
|
196+
help: use fully-qualified syntax
197+
|
198+
LL | type H = <(dyn Fn(u8) -> u8 + 'static) as AsyncFnOnce>::Output;
199+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
200+
LL | type H = <(dyn Fn(u8) -> u8 + 'static) as IntoFuture>::Output;
201+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
195202

196203
error[E0223]: ambiguous associated type
197204
--> $DIR/bad-assoc-ty.rs:39:19

0 commit comments

Comments
 (0)