forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rollup merge of rust-lang#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).
- Loading branch information
Showing
7 changed files
with
157 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters