mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 16:24:46 +00:00
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).
This commit is contained in:
commit
8c6cf3c934
@ -208,6 +208,10 @@ language_item_table! {
|
|||||||
FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
|
FnMut, sym::fn_mut, fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||||
FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
|
FnOnce, sym::fn_once, fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||||
|
|
||||||
|
AsyncFn, sym::async_fn, async_fn_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||||
|
AsyncFnMut, sym::async_fn_mut, async_fn_mut_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||||
|
AsyncFnOnce, sym::async_fn_once, async_fn_once_trait, Target::Trait, GenericRequirement::Exact(1);
|
||||||
|
|
||||||
FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None;
|
FnOnceOutput, sym::fn_once_output, fn_once_output, Target::AssocTy, GenericRequirement::None;
|
||||||
|
|
||||||
Iterator, sym::iterator, iterator_trait, Target::Trait, GenericRequirement::Exact(0);
|
Iterator, sym::iterator, iterator_trait, Target::Trait, GenericRequirement::Exact(0);
|
||||||
|
@ -220,6 +220,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||||||
(self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true),
|
(self.tcx.lang_items().fn_trait(), Ident::with_dummy_span(sym::call), true),
|
||||||
(self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true),
|
(self.tcx.lang_items().fn_mut_trait(), Ident::with_dummy_span(sym::call_mut), true),
|
||||||
(self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false),
|
(self.tcx.lang_items().fn_once_trait(), Ident::with_dummy_span(sym::call_once), false),
|
||||||
|
(self.tcx.lang_items().async_fn_trait(), Ident::with_dummy_span(sym::async_call), true),
|
||||||
|
(
|
||||||
|
self.tcx.lang_items().async_fn_mut_trait(),
|
||||||
|
Ident::with_dummy_span(sym::async_call_mut),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
self.tcx.lang_items().async_fn_once_trait(),
|
||||||
|
Ident::with_dummy_span(sym::async_call_once),
|
||||||
|
false,
|
||||||
|
),
|
||||||
] {
|
] {
|
||||||
let Some(trait_def_id) = opt_trait_def_id else { continue };
|
let Some(trait_def_id) = opt_trait_def_id else { continue };
|
||||||
|
|
||||||
|
@ -425,8 +425,14 @@ symbols! {
|
|||||||
assume,
|
assume,
|
||||||
assume_init,
|
assume_init,
|
||||||
async_await,
|
async_await,
|
||||||
|
async_call,
|
||||||
|
async_call_mut,
|
||||||
|
async_call_once,
|
||||||
async_closure,
|
async_closure,
|
||||||
|
async_fn,
|
||||||
async_fn_in_trait,
|
async_fn_in_trait,
|
||||||
|
async_fn_mut,
|
||||||
|
async_fn_once,
|
||||||
async_fn_track_caller,
|
async_fn_track_caller,
|
||||||
async_for_loop,
|
async_for_loop,
|
||||||
async_iterator,
|
async_iterator,
|
||||||
|
108
library/core/src/ops/async_function.rs
Normal file
108
library/core/src/ops/async_function.rs
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
use crate::future::Future;
|
||||||
|
use crate::marker::Tuple;
|
||||||
|
|
||||||
|
/// An async-aware version of the [`Fn`](crate::ops::Fn) trait.
|
||||||
|
///
|
||||||
|
/// All `async fn` and functions returning futures implement this trait.
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
#[rustc_paren_sugar]
|
||||||
|
#[fundamental]
|
||||||
|
#[must_use = "async closures are lazy and do nothing unless called"]
|
||||||
|
#[cfg_attr(not(bootstrap), lang = "async_fn")]
|
||||||
|
pub trait AsyncFn<Args: Tuple>: AsyncFnMut<Args> {
|
||||||
|
/// Future returned by [`AsyncFn::async_call`].
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
type CallFuture<'a>: Future<Output = Self::Output>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
/// Call the [`AsyncFn`], returning a future which may borrow from the called closure.
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
extern "rust-call" fn async_call(&self, args: Args) -> Self::CallFuture<'_>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An async-aware version of the [`FnMut`](crate::ops::FnMut) trait.
|
||||||
|
///
|
||||||
|
/// All `async fn` and functions returning futures implement this trait.
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
#[rustc_paren_sugar]
|
||||||
|
#[fundamental]
|
||||||
|
#[must_use = "async closures are lazy and do nothing unless called"]
|
||||||
|
#[cfg_attr(not(bootstrap), lang = "async_fn_mut")]
|
||||||
|
pub trait AsyncFnMut<Args: Tuple>: AsyncFnOnce<Args> {
|
||||||
|
/// Future returned by [`AsyncFnMut::async_call_mut`].
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
type CallMutFuture<'a>: Future<Output = Self::Output>
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
/// Call the [`AsyncFnMut`], returning a future which may borrow from the called closure.
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
extern "rust-call" fn async_call_mut(&mut self, args: Args) -> Self::CallMutFuture<'_>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An async-aware version of the [`FnOnce`](crate::ops::FnOnce) trait.
|
||||||
|
///
|
||||||
|
/// All `async fn` and functions returning futures implement this trait.
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
#[rustc_paren_sugar]
|
||||||
|
#[fundamental]
|
||||||
|
#[must_use = "async closures are lazy and do nothing unless called"]
|
||||||
|
#[cfg_attr(not(bootstrap), lang = "async_fn_once")]
|
||||||
|
pub trait AsyncFnOnce<Args: Tuple> {
|
||||||
|
/// Future returned by [`AsyncFnOnce::async_call_once`].
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
type CallOnceFuture: Future<Output = Self::Output>;
|
||||||
|
|
||||||
|
/// Output type of the called closure's future.
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
/// Call the [`AsyncFnOnce`], returning a future which may move out of the called closure.
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
extern "rust-call" fn async_call_once(self, args: Args) -> Self::CallOnceFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod impls {
|
||||||
|
use super::{AsyncFn, AsyncFnMut, AsyncFnOnce};
|
||||||
|
use crate::future::Future;
|
||||||
|
use crate::marker::Tuple;
|
||||||
|
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
impl<F: Fn<A>, A: Tuple> AsyncFn<A> for F
|
||||||
|
where
|
||||||
|
<F as FnOnce<A>>::Output: Future,
|
||||||
|
{
|
||||||
|
type CallFuture<'a> = <F as FnOnce<A>>::Output where Self: 'a;
|
||||||
|
|
||||||
|
extern "rust-call" fn async_call(&self, args: A) -> Self::CallFuture<'_> {
|
||||||
|
self.call(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
impl<F: FnMut<A>, A: Tuple> AsyncFnMut<A> for F
|
||||||
|
where
|
||||||
|
<F as FnOnce<A>>::Output: Future,
|
||||||
|
{
|
||||||
|
type CallMutFuture<'a> = <F as FnOnce<A>>::Output where Self: 'a;
|
||||||
|
|
||||||
|
extern "rust-call" fn async_call_mut(&mut self, args: A) -> Self::CallMutFuture<'_> {
|
||||||
|
self.call_mut(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
impl<F: FnOnce<A>, A: Tuple> AsyncFnOnce<A> for F
|
||||||
|
where
|
||||||
|
<F as FnOnce<A>>::Output: Future,
|
||||||
|
{
|
||||||
|
type CallOnceFuture = <F as FnOnce<A>>::Output;
|
||||||
|
|
||||||
|
type Output = <<F as FnOnce<A>>::Output as Future>::Output;
|
||||||
|
|
||||||
|
extern "rust-call" fn async_call_once(self, args: A) -> Self::CallOnceFuture {
|
||||||
|
self.call_once(args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -139,6 +139,7 @@
|
|||||||
#![stable(feature = "rust1", since = "1.0.0")]
|
#![stable(feature = "rust1", since = "1.0.0")]
|
||||||
|
|
||||||
mod arith;
|
mod arith;
|
||||||
|
mod async_function;
|
||||||
mod bit;
|
mod bit;
|
||||||
mod control_flow;
|
mod control_flow;
|
||||||
mod coroutine;
|
mod coroutine;
|
||||||
@ -173,6 +174,9 @@ pub use self::drop::Drop;
|
|||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub use self::function::{Fn, FnMut, FnOnce};
|
pub use self::function::{Fn, FnMut, FnOnce};
|
||||||
|
|
||||||
|
#[unstable(feature = "async_fn_traits", issue = "none")]
|
||||||
|
pub use self::async_function::{AsyncFn, AsyncFnMut, AsyncFnOnce};
|
||||||
|
|
||||||
#[stable(feature = "rust1", since = "1.0.0")]
|
#[stable(feature = "rust1", since = "1.0.0")]
|
||||||
pub use self::index::{Index, IndexMut};
|
pub use self::index::{Index, IndexMut};
|
||||||
|
|
||||||
|
16
tests/ui/async-await/async-fn/simple.rs
Normal file
16
tests/ui/async-await/async-fn/simple.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// edition: 2021
|
||||||
|
// check-pass
|
||||||
|
|
||||||
|
#![feature(async_fn_traits)]
|
||||||
|
|
||||||
|
use std::ops::AsyncFn;
|
||||||
|
|
||||||
|
async fn foo() {}
|
||||||
|
|
||||||
|
async fn call_asyncly(f: impl AsyncFn(i32) -> i32) -> i32 {
|
||||||
|
f(1).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let fut = call_asyncly(|x| async move { x + 1 });
|
||||||
|
}
|
@ -191,7 +191,14 @@ error[E0223]: ambiguous associated type
|
|||||||
--> $DIR/bad-assoc-ty.rs:33:10
|
--> $DIR/bad-assoc-ty.rs:33:10
|
||||||
|
|
|
|
||||||
LL | type H = Fn(u8) -> (u8)::Output;
|
LL | type H = Fn(u8) -> (u8)::Output;
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^ help: use fully-qualified syntax: `<(dyn Fn(u8) -> u8 + 'static) as IntoFuture>::Output`
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
help: use fully-qualified syntax
|
||||||
|
|
|
||||||
|
LL | type H = <(dyn Fn(u8) -> u8 + 'static) as AsyncFnOnce>::Output;
|
||||||
|
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
LL | type H = <(dyn Fn(u8) -> u8 + 'static) as IntoFuture>::Output;
|
||||||
|
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
error[E0223]: ambiguous associated type
|
error[E0223]: ambiguous associated type
|
||||||
--> $DIR/bad-assoc-ty.rs:39:19
|
--> $DIR/bad-assoc-ty.rs:39:19
|
||||||
|
Loading…
Reference in New Issue
Block a user