-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
async/await #51580
async/await #51580
Conversation
Why are My inclination would be to try to make the feature gating as pleasant as possible for users; I'd prefer merging both compiler features into #![feature(async_await, futures)] (assuming they're using a library that handles pinning for them as a part of its execution API). |
| | | ||
| first lifetime here | ||
| | ||
= help: `async fn` can only accept borrowed values identical lifetimes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
async fn
can only accept borrowed values with identical lifetimes (<-- added "with")
src/libsyntax/parse/parser.rs
Outdated
@@ -6755,6 +6807,28 @@ impl<'a> Parser<'a> { | |||
maybe_append(attrs, extra_attrs)); | |||
return Ok(Some(item)); | |||
} | |||
if self.check_keyword(keywords::Async) && | |||
(self.look_ahead(1, |t| t.is_keyword(keywords::Fn)) || | |||
self.look_ahead(1, |t| t.is_keyword(keywords::Unsafe))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order a lot of people seemed to agree on in the RFC discussion was different. rust-lang/rfcs#2394 (comment)
"const unsafe async move
": unsafe
was in front of async
back then (of course move
doesn't apply here) To me unsafe
in front of async
seems more "natural" because unsafety should be featured more prominently.
I notice this is still using TLS for passing the context in to the generated futures. Has resurrecting generator arguments for this been considered? (big reason I want this is to easily support |
@Nemo157 as far as I know, we definitely want to stop using TLS in the long run (before we stabilize anything), and the current use in this PR is a hack to get the next iteration of async/await up and running. |
@Nemo157 @withoutboats is absolutely correct-- we definitely want to support no-TLS and no-std |
One thing that occurred to me: |
@cramertj why would we want to experiment on the 2015 edition over the 2018 edition (since both are available on nightly)? In the final stable version I think I'd like to make things consistent by not support any async and await features on 2015 (so that the story is very simple, instead of having to remember which half of the features are available on 2015). But if its expedient to support some things on 2015 in this iteration, that seems fine. |
Because it would allow for specifically testing out async/await without having to change the rest of the project over to the 2018 edition. |
Currently standard library features must be gated separately from language features. This is also why |
@cramertj I didn't see it was literally implemented as a macro. We don't intend to stabilize it with the current syntax anyway, do you object to using the |
Hm... it seems sort of surprising to me that |
This comment has been minimized.
This comment has been minimized.
@@ -213,6 +213,26 @@ macro_rules! eprintln { | |||
($fmt:expr, $($arg:tt)*) => (eprint!(concat!($fmt, "\n"), $($arg)*)); | |||
} | |||
|
|||
#[macro_export] | |||
#[unstable(feature = "await_macro", issue = "50547")] | |||
#[allow_internal_unstable] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably use #[allow_internal_unsafe]
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/libsyntax/ptr.rs
Outdated
@@ -95,6 +95,16 @@ impl<T: 'static> P<T> { | |||
} | |||
} | |||
|
|||
impl<T: 'static> P<[T]> { | |||
pub fn map_slice<F>(self, f: F) -> P<[T]> where | |||
F: FnOnce(Vec<T>) -> Vec<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks similar to existing move_map
/move_flat_map
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is similar, but move_map
and move_flat_map
expect to receive one element at a time, whereas this takes the whole list at once, allowing it to do things like modifying the last element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be able to remove this now.
src/libsyntax/parse/parser.rs
Outdated
@@ -2246,6 +2258,15 @@ impl<'a> Parser<'a> { | |||
hi = path.span; | |||
return Ok(self.mk_expr(lo.to(hi), ExprKind::Path(Some(qself), path), attrs)); | |||
} | |||
if syntax_pos::hygiene::default_edition() >= Edition::Edition2018 && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably want self.token.span.edition() >= Edition::Edition2018
here instead of default edition in case async
comes from a macro defined with other edition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean self.span.edition()
? Token
doesn't have a span
field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, sorry (self.span
is the span of self.token
).
src/libsyntax/parse/parser.rs
Outdated
@@ -3240,6 +3261,13 @@ impl<'a> Parser<'a> { | |||
} else { | |||
Movability::Movable | |||
}; | |||
let asyncness = if syntax_pos::hygiene::default_edition() >= Edition::Edition2018 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsAsync::Async(ast::DUMMY_NODE_ID) | ||
} else { | ||
IsAsync::NotAsync | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it's fixed-order static async move |args...| body
now.
The number of modifiers becomes closer to the point where nobody can remember the correct order :)
@@ -4274,6 +4320,18 @@ impl<'a> Parser<'a> { | |||
}) | |||
} | |||
|
|||
fn is_async_block(&mut self) -> bool { | |||
self.token.is_keyword(keywords::Async) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This is kind of fragile due to assumption that edition check was already performed and the function is used only once, so it may be better to just inline it.
// `unsafe async fn` or `async fn` | ||
if ( | ||
self.check_keyword(keywords::Unsafe) && | ||
self.look_ahead(1, |t| t.is_keyword(keywords::Async)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like edition checks are missing here and below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's intentional-- async fn
is supposed to work on edition 2015. It's just closures and blocks that don't.
let is_const_fn = self.eat_keyword(keywords::Const); | ||
let const_span = self.prev_span; | ||
let unsafety = self.parse_unsafety(); | ||
let asyncness = self.parse_asyncness(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the syntax is fixed-order default const unsafe async extern "C" fn foo() {}
now.
Fascinating.
There are two groups of things here - stuff inherent to the fn type (unsafe
, extern "ABI"
and fn
itself) and possibly context-dependent "modifiers" (default
, const
, async
).
So far the logic was that modifiers were grouped together on the left, and type-things were grouped together with fn
on the right - default const | unsafe extern "C" fn
so the modifiers can't get "inside the fn type" type F = unsafe extern "C" fn();
.
I think we should turn the syntax into (default const async in arbitrary order) | (unsafe extern "C" in arbitrary order) fn
eventually, but for now let's keep it fixed-order while keeping the grouping rule - default const async | unsafe extern "C" fn
.
Reviewed. |
Thanks @petrochenkov! |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
There always has been one: you take the current biggest and you add one. Otherwise it's nearly impossible to keep track of what's going on. Anyway, I'll open a PR to fix it... |
@guillaumegomes I've never done that because it means you have to constantly edit your PR when someone else merges something using the same error codes. |
It doesn't happen very often, and in addition to that, it's a very small change. Going from 700 to 900+ is kind of a big step. |
+1, that's the reason I don't add errors with codes at all and leave that for separate error-code-assigning PRs |
@petrochenkov: That's pretty bad but whatever. If no one cares I'll just stop doing it at some point... |
@GuillaumeGomez Error codes are a complete disaster IMO and I'd argue we should just remove them and figure out another indexing mechanism. Every single time someone mentions an error by code, I have to ask them to replace the code with something readable. |
Sorry for being off-topic (on the digression into error codes), and I admit that I have no idea about any of this, but has anyone suggested or brought up using some kind of short, descriptive error identifiers (I'll call them "shortcodes") instead of opaque numeric error codes? Something like eslint rules. Yes, an error code isn't meant to be a self-contained exhaustive explanation of an error, but I find "shortcodes" to be qualitatively better than basic numeric codes. Whenever I see them emitted by eslint, they're much more helpful to me than numeric error codes (emitted by TypeScript for example), they're usually enough for me to remember what the error/mistake pertains to so that I can quickly fix it without having to spend time (even if mere seconds) deciphering the compiler/linter output, but in the unlikely event that that's not the case, then of course I could still look it up since the shortcode is uniquely identifying (for example no-dupe-args). For example, the error codes from this PR:
Might become (first draft, see below):
Some others might be straightforward: - E0300, // unexpanded macro
+ unexpanded-macro, // unexpanded macro Others maybe trickier: - E0315, // cannot invoke closure outside of its lifetime
+ outlived-closure-invocation, // cannot invoke closure outside of its lifetime Obviously as a first draft that may not look great. I admit that I don't have a deep enough understanding of the errors in order to name them both precisely and concisely, and I'm sure people can pile on with bikeshedding those particular codes, but the idea is that some convention/standard would evolve around how to abbreviate/shorten certain words, what prefixes/suffixes to use, what words to use to connote/convey different types of errors/mistakes, and so on, such that each code would be somewhat uniform/consistent There would preferably be no hard limit on the length as that may eventually lead to overly terse error codes due to unforeseen future Rust terminology or overly niche/specific error situations, defeating the point of them in the first place, but obviously there would be a very strong preference toward short and concise while balancing for descriptive, and definitely unique. Yes, it's unlikely that any one scheme would be perfect, but "shortcodes" may serve more useful and readable than clearly opaque (to end users) numeric error codes, and would at least as one consequence remedy this PR contribution/conflict situation. I'm not sure how error codes relate to backwards compatibility, or whether this can ever be done going forward, even at the edition level. Maybe the ship has long since sailed on this and it's not feasible to introduce something like this at this point. |
@eddyb: I agree on your points but as long as a new (better?) system is put in place, let's just keep to stop destroying the current one, as bad as it might seems. It'll make the migration simpler. |
This appears to have uncovered a miscompilation bug on old macOS: servo/servo#21089 (comment). I haven’t managed to run lldb so far, but both affected build scripts use |
I suspect this PR broke |
FWIW, the direct equivalent is our lint names. I'm not sure we need identifiers at all, although ones that could be name types would be better, since we could use them for structural errors. |
Addresses the errors produced by (re)moving, merging or renaming structs, fields and methods by rust-lang/rust#48149 and rust-lang/rust#51580
@blaenk That is indeed very off-topic for this. You should create a new issue if you want further discussion of it. One remark I will make: some tooling is designed around using numbers. Vim’s 'errorformat' looks for error numbers and treats them specially. Very mild damage would be done there if that were changed. |
…i-obk Stabilize async closures (RFC 3668) # Async Closures Stabilization Report This report proposes the stabilization of `#![feature(async_closure)]` ([RFC 3668](https://rust-lang.github.io/rfcs/3668-async-closures.html)). This is a long-awaited feature that increases the expressiveness of the Rust language and fills a pressing gap in the async ecosystem. ## Stabilization summary * You can write async closures like `async || {}` which return futures that can borrow from their captures and can be higher-ranked in their argument lifetimes. * You can express trait bounds for these async closures using the `AsyncFn` family of traits, analogous to the `Fn` family. ```rust async fn takes_an_async_fn(f: impl AsyncFn(&str)) { futures::join(f("hello"), f("world")).await; } takes_an_async_fn(async |s| { other_fn(s).await }).await; ``` ## Motivation Without this feature, users hit two major obstacles when writing async code that uses closures and `Fn` trait bounds: - The inability to express higher-ranked async function signatures. - That closures cannot return futures that borrow from the closure captures. That is, for the first, we cannot write: ```rust // We cannot express higher-ranked async function signatures. async fn f<Fut>(_: impl for<'a> Fn(&'a u8) -> Fut) where Fut: Future<Output = ()>, { todo!() } async fn main() { async fn g(_: &u8) { todo!() } f(g).await; //~^ ERROR mismatched types //~| ERROR one type is more general than the other } ``` And for the second, we cannot write: ```rust // Closures cannot return futures that borrow closure captures. async fn f<Fut: Future<Output = ()>>(_: impl FnMut() -> Fut) { todo!() } async fn main() { let mut xs = vec![]; f(|| async { async fn g() -> u8 { todo!() } xs.push(g().await); }); //~^ ERROR captured variable cannot escape `FnMut` closure body } ``` Async closures provide a first-class solution to these problems. For further background, please refer to the [motivation section](https://rust-lang.github.io/rfcs/3668-async-closures.html#motivation) of the RFC. ## Major design decisions since RFC The RFC had left open the question of whether we would spell the bounds syntax for async closures... ```rust // ...as this... fn f() -> impl AsyncFn() -> u8 { todo!() } // ...or as this: fn f() -> impl async Fn() -> u8 { todo!() } ``` We've decided to spell this as `AsyncFn{,Mut,Once}`. The `Fn` family of traits is special in many ways. We had originally argued that, due to this specialness, that perhaps the `async Fn` syntax could be adopted without having to decide whether a general `async Trait` mechanism would ever be adopted. However, concerns have been raised that we may not want to use `async Fn` syntax unless we would pursue more general trait modifiers. Since there remain substantial open questions on those -- and we don't want to rush any design work there -- it makes sense to ship this needed feature using the `AsyncFn`-style bounds syntax. Since we would, in no case, be shipping a generalized trait modifier system anytime soon, we'll be continuing to see `AsyncFoo` traits appear across the ecosystem regardless. If we were to ever later ship some general mechanism, we could at that time manage the migration from `AsyncFn` to `async Fn`, just as we'd be enabling and managing the migration of many other traits. Note that, as specified in RFC 3668, the details of the `AsyncFn*` traits are not exposed and they can only be named via the "parentheses sugar". That is, we can write `T: AsyncFn() -> u8` but not `T: AsyncFn<Output = u8>`. Unlike the `Fn` traits, we cannot project to the `Output` associated type of the `AsyncFn` traits. That is, while we can write... ```rust fn f<F: Fn() -> u8>(_: F::Output) {} ``` ...we cannot write: ```rust fn f<F: AsyncFn() -> u8>(_: F::Output) {} //~^ ERROR ``` The choice of `AsyncFn{,Mut,Once}` bounds syntax obviates, for our purposes here, another question decided after that RFC, which was how to order bound modifiers such as `for<'a> async Fn()`. Other than answering the open question in the RFC on syntax, nothing has changed about the design of this feature between RFC 3668 and this stabilization. ## What is stabilized For those interested in the technical details, please see [the dev guide section](https://rustc-dev-guide.rust-lang.org/coroutine-closures.html) I authored. #### Async closures Other than in how they solve the problems described above, async closures act similarly to closures that return async blocks, and can have parts of their signatures specified: ```rust // They can have arguments annotated with types: let _ = async |_: u8| { todo!() }; // They can have their return types annotated: let _ = async || -> u8 { todo!() }; // They can be higher-ranked: let _ = async |_: &str| { todo!() }; // They can capture values by move: let x = String::from("hello, world"); let _ = async move || do_something(&x).await }; ``` When called, they return an anonymous future type corresponding to the (not-yet-executed) body of the closure. These can be awaited like any other future. What distinguishes async closures is that, unlike closures that return async blocks, the futures returned from the async closure can capture state from the async closure. For example: ```rust let vec: Vec<String> = vec![]; let closure = async || { vec.push(ready(String::from("")).await); }; ``` The async closure captures `vec` with some `&'closure mut Vec<String>` which lives until the closure is dropped. Every call to `closure()` returns a future which reborrows that mutable reference `&'call mut Vec<String>` which lives until the future is dropped (e.g. it is `await`ed). As another example: ```rust let string: String = "Hello, world".into(); let closure = async move || { ready(&string).await; }; ``` The closure is marked with `move`, which means it takes ownership of the string by *value*. The future that is returned by calling `closure()` returns a future which borrows a reference `&'call String` which lives until the future is dropped (e.g. it is `await`ed). #### Async fn trait family To support the lending capability of async closures, and to provide a first-class way to express higher-ranked async closures, we introduce the `AsyncFn*` family of traits. See the [corresponding section](https://rust-lang.github.io/rfcs/3668-async-closures.html#asyncfn) of the RFC. We stabilize naming `AsyncFn*` via the "parenthesized sugar" syntax that normal `Fn*` traits can be named. The `AsyncFn*` trait can be used anywhere a `Fn*` trait bound is allowed, such as: ```rust /// In return-position impl trait: fn closure() -> impl AsyncFn() { async || {} } /// In trait bounds: trait Foo<F>: Sized where F: AsyncFn() { fn new(f: F) -> Self; } /// in GATs: trait Gat { type AsyncHasher<T>: AsyncFn(T) -> i32; } ``` Other than using them in trait bounds, the definitions of these traits are not directly observable, but certain aspects of their behavior can be indirectly observed such as the fact that: * `AsyncFn::async_call` and `AsyncFnMut::async_call_mut` return a future which is *lending*, and therefore borrows the `&self` lifetime of the callee. ```rust fn by_ref_call(c: impl AsyncFn()) { let fut = c(); drop(c); // ^ Cannot drop `c` since it is borrowed by `fut`. } ``` * `AsyncFnOnce::async_call_once` returns a future that takes ownership of the callee. ```rust fn by_ref_call(c: impl AsyncFnOnce()) { let fut = c(); let _ = c(); // ^ Cannot call `c` since calling it takes ownership the callee. } ``` * All currently-stable callable types (i.e., closures, function items, function pointers, and `dyn Fn*` trait objects) automatically implement `AsyncFn*() -> T` if they implement `Fn*() -> Fut` for some output type `Fut`, and `Fut` implements `Future<Output = T>`. * This is to make sure that `AsyncFn*()` trait bounds have maximum compatibility with existing callable types which return futures, such as async function items and closures which return boxed futures. * For now, this only works currently for *concrete* callable types -- for example, a argument-position impl trait like `impl Fn() -> impl Future<Output = ()>` does not implement `AsyncFn()`, due to the fact that a `AsyncFn`-if-`Fn` blanket impl does not exist in reality. This may be relaxed in the future. Users can work around this by wrapping their type in an async closure and calling it. I expect this to not matter much in practice, as users are encouraged to write `AsyncFn` bounds directly. ```rust fn is_async_fn(_: impl AsyncFn(&str)) {} async fn async_fn_item(s: &str) { todo!() } is_async_fn(s); // ^^^ This works. fn generic(f: impl Fn() -> impl Future<Output = ()>) { is_async_fn(f); // ^^^ This does not work (yet). } ``` #### The by-move future When async closures are called with `AsyncFn`/`AsyncFnMut`, they return a coroutine that borrows from the closure. However, when they are called via `AsyncFnOnce`, we consume that closure, and cannot return a coroutine that borrows from data that is now dropped. To work around around this limitation, we synthesize a separate future type for calling the async closure via `AsyncFnOnce`. This future executes identically to the by-ref future returned from calling the async closure, except for the fact that it has a different set of captures, since we must *move* the captures from the parent async into the child future. #### Interactions between async closures and the `Fn*` family of traits Async closures always implement `FnOnce`, since they always can be called once. They may also implement `Fn` or `FnMut` if their body is compatible with the calling mode (i.e. if they do not mutate their captures, or they do not capture their captures, respectively) and if the future returned by the async closure is not *lending*. ```rust let id = String::new(); let mapped: Vec</* impl Future */> = [/* elements */] .into_iter() // `Iterator::map` takes an `impl FnMut` .map(async |element| { do_something(&id, element).await; }) .collect(); ``` See [the dev guide](https://rustc-dev-guide.rust-lang.org/coroutine-closures.html#follow-up-when-do-async-closures-implement-the-regular-fn-traits) for a detailed explanation for the situations where this may not be possible due to the lending nature of async closures. #### Other notable features of async closures shared with synchronous closures * Async closures are `Copy` and/or `Clone` if their captures are `Copy`/`Clone`. * Async closures do closure signature inference: If an async closure is passed to a function with a `AsyncFn` or `Fn` trait bound, we can eagerly infer the argument types of the closure. More details are provided in [the dev guide](https://rustc-dev-guide.rust-lang.org/coroutine-closures.html#closure-signature-inference). #### Lints This PR also stabilizes the `CLOSURE_RETURNING_ASYNC_BLOCK` lint as an `allow` lint. This lints on "old-style" async closures: ```rust #![warn(closure_returning_async_block)] let c = |x: &str| async {}; ``` We should encourage users to use `async || {}` where possible. This lint remains `allow` and may be refined in the future because it has a few false positives (namely, see: "Where do we expect rewriting `|| async {}` into `async || {}` to fail?") An alternative that could be made at the time of stabilization is to put this lint behind another gate, so we can decide to stabilize it later. ## What isn't stabilized (aka, potential future work) #### `async Fn*()` bound syntax We decided to stabilize async closures without the `async Fn*()` bound modifier syntax. The general direction of this syntax and how it fits is still being considered by T-lang (e.g. in [RFC 3710](rust-lang/rfcs#3710)). #### Naming the futures returned by async closures This stabilization PR does not provide a way of naming the futures returned by calling `AsyncFn*`. Exposing a stable way to refer to these futures is important for building async-closure-aware combinators, and will be an important future step. #### Return type notation-style bounds for async closures The RFC described an RTN-like syntax for putting bounds on the future returned by an async closure: ```rust async fn foo(x: F) -> Result<()> where F: AsyncFn(&str) -> Result<()>, // The future from calling `F` is `Send` and `'static`. F(..): Send + 'static, {} ``` This stabilization PR does not stabilize that syntax yet, which remains unimplemented (though will be soon). #### `dyn AsyncFn*()` `AsyncFn*` are not dyn-compatible yet. This will likely be implemented in the future along with the dyn-compatibility of async fn in trait, since the same issue (dealing with the future returned by a call) applies there. ## Tests Tests exist for this feature in [`tests/ui/async-await/async-closures`](/~https://github.com/rust-lang/rust/tree/5b542866400ad4a294f468cfa7e059d95c27a079/tests/ui/async-await/async-closures). <details> <summary>A selected set of tests:</summary> * Lending behavior of async closures * `tests/ui/async-await/async-closures/mutate.rs` * `tests/ui/async-await/async-closures/captures.rs` * `tests/ui/async-await/async-closures/precise-captures.rs` * `tests/ui/async-await/async-closures/no-borrow-from-env.rs` * Async closures may be higher-ranked * `tests/ui/async-await/async-closures/higher-ranked.rs` * `tests/ui/async-await/async-closures/higher-ranked-return.rs` * Async closures may implement `Fn*` traits * `tests/ui/async-await/async-closures/is-fn.rs` * `tests/ui/async-await/async-closures/implements-fnmut.rs` * Async closures may be cloned * `tests/ui/async-await/async-closures/clone-closure.rs` * Ownership of the upvars when `AsyncFnOnce` is called * `tests/ui/async-await/async-closures/drop.rs` * `tests/ui/async-await/async-closures/move-is-async-fn.rs` * `tests/ui/async-await/async-closures/force-move-due-to-inferred-kind.rs` * `tests/ui/async-await/async-closures/force-move-due-to-actually-fnonce.rs` * Closure signature inference * `tests/ui/async-await/async-closures/signature-deduction.rs` * `tests/ui/async-await/async-closures/sig-from-bare-fn.rs` * `tests/ui/async-await/async-closures/signature-inference-from-two-part-bound.rs` </details> ## Remaining bugs and open issues * rust-lang#120694 tracks moving onto more general `LendingFn*` traits. No action needed, since it's not observable. * rust-lang#124020 - Polymorphization ICE. Polymorphization needs to be heavily reworked. No action needed. * rust-lang#127227 - Tracking reworking the way that rustdoc re-sugars bounds. * The part relevant to to `AsyncFn` is fixed by rust-lang#132697. ## Where do we expect rewriting `|| async {}` into `async || {}` to fail? * Fn pointer coercions * Currently, it is not possible to coerce an async closure to an fn pointer like regular closures can be. This functionality may be implemented in the future. ```rust let x: fn() -> _ = async || {}; ``` * Argument capture * Like async functions, async closures always capture their input arguments. This is in contrast to something like `|t: T| async {}`, which doesn't capture `t` unless it is used in the async block. This may affect the `Send`-ness of the future or affect its outlives. ```rust fn needs_send_future(_: impl Fn(NotSendArg) -> Fut) where Fut: Future<Output = ()>, {} needs_send_future(async |_| {}); ``` ## History #### Important feature history - rust-lang#51580 - rust-lang#62292 - rust-lang#120361 - rust-lang#120712 - rust-lang#121857 - rust-lang#123660 - rust-lang#125259 - rust-lang#128506 - rust-lang#127482 ## Acknowledgements Thanks to `@oli-obk` for reviewing the bulk of the work for this feature. Thanks to `@nikomatsakis` for his design blog posts which generated interest for this feature, `@traviscross` for feedback and additions to this stabilization report. All errors are my own. r? `@ghost`
Stabilize async closures (RFC 3668) # Async Closures Stabilization Report This report proposes the stabilization of `#![feature(async_closure)]` ([RFC 3668](https://rust-lang.github.io/rfcs/3668-async-closures.html)). This is a long-awaited feature that increases the expressiveness of the Rust language and fills a pressing gap in the async ecosystem. ## Stabilization summary * You can write async closures like `async || {}` which return futures that can borrow from their captures and can be higher-ranked in their argument lifetimes. * You can express trait bounds for these async closures using the `AsyncFn` family of traits, analogous to the `Fn` family. ```rust async fn takes_an_async_fn(f: impl AsyncFn(&str)) { futures::join(f("hello"), f("world")).await; } takes_an_async_fn(async |s| { other_fn(s).await }).await; ``` ## Motivation Without this feature, users hit two major obstacles when writing async code that uses closures and `Fn` trait bounds: - The inability to express higher-ranked async function signatures. - That closures cannot return futures that borrow from the closure captures. That is, for the first, we cannot write: ```rust // We cannot express higher-ranked async function signatures. async fn f<Fut>(_: impl for<'a> Fn(&'a u8) -> Fut) where Fut: Future<Output = ()>, { todo!() } async fn main() { async fn g(_: &u8) { todo!() } f(g).await; //~^ ERROR mismatched types //~| ERROR one type is more general than the other } ``` And for the second, we cannot write: ```rust // Closures cannot return futures that borrow closure captures. async fn f<Fut: Future<Output = ()>>(_: impl FnMut() -> Fut) { todo!() } async fn main() { let mut xs = vec![]; f(|| async { async fn g() -> u8 { todo!() } xs.push(g().await); }); //~^ ERROR captured variable cannot escape `FnMut` closure body } ``` Async closures provide a first-class solution to these problems. For further background, please refer to the [motivation section](https://rust-lang.github.io/rfcs/3668-async-closures.html#motivation) of the RFC. ## Major design decisions since RFC The RFC had left open the question of whether we would spell the bounds syntax for async closures... ```rust // ...as this... fn f() -> impl AsyncFn() -> u8 { todo!() } // ...or as this: fn f() -> impl async Fn() -> u8 { todo!() } ``` We've decided to spell this as `AsyncFn{,Mut,Once}`. The `Fn` family of traits is special in many ways. We had originally argued that, due to this specialness, that perhaps the `async Fn` syntax could be adopted without having to decide whether a general `async Trait` mechanism would ever be adopted. However, concerns have been raised that we may not want to use `async Fn` syntax unless we would pursue more general trait modifiers. Since there remain substantial open questions on those -- and we don't want to rush any design work there -- it makes sense to ship this needed feature using the `AsyncFn`-style bounds syntax. Since we would, in no case, be shipping a generalized trait modifier system anytime soon, we'll be continuing to see `AsyncFoo` traits appear across the ecosystem regardless. If we were to ever later ship some general mechanism, we could at that time manage the migration from `AsyncFn` to `async Fn`, just as we'd be enabling and managing the migration of many other traits. Note that, as specified in RFC 3668, the details of the `AsyncFn*` traits are not exposed and they can only be named via the "parentheses sugar". That is, we can write `T: AsyncFn() -> u8` but not `T: AsyncFn<Output = u8>`. Unlike the `Fn` traits, we cannot project to the `Output` associated type of the `AsyncFn` traits. That is, while we can write... ```rust fn f<F: Fn() -> u8>(_: F::Output) {} ``` ...we cannot write: ```rust fn f<F: AsyncFn() -> u8>(_: F::Output) {} //~^ ERROR ``` The choice of `AsyncFn{,Mut,Once}` bounds syntax obviates, for our purposes here, another question decided after that RFC, which was how to order bound modifiers such as `for<'a> async Fn()`. Other than answering the open question in the RFC on syntax, nothing has changed about the design of this feature between RFC 3668 and this stabilization. ## What is stabilized For those interested in the technical details, please see [the dev guide section](https://rustc-dev-guide.rust-lang.org/coroutine-closures.html) I authored. #### Async closures Other than in how they solve the problems described above, async closures act similarly to closures that return async blocks, and can have parts of their signatures specified: ```rust // They can have arguments annotated with types: let _ = async |_: u8| { todo!() }; // They can have their return types annotated: let _ = async || -> u8 { todo!() }; // They can be higher-ranked: let _ = async |_: &str| { todo!() }; // They can capture values by move: let x = String::from("hello, world"); let _ = async move || do_something(&x).await }; ``` When called, they return an anonymous future type corresponding to the (not-yet-executed) body of the closure. These can be awaited like any other future. What distinguishes async closures is that, unlike closures that return async blocks, the futures returned from the async closure can capture state from the async closure. For example: ```rust let vec: Vec<String> = vec![]; let closure = async || { vec.push(ready(String::from("")).await); }; ``` The async closure captures `vec` with some `&'closure mut Vec<String>` which lives until the closure is dropped. Every call to `closure()` returns a future which reborrows that mutable reference `&'call mut Vec<String>` which lives until the future is dropped (e.g. it is `await`ed). As another example: ```rust let string: String = "Hello, world".into(); let closure = async move || { ready(&string).await; }; ``` The closure is marked with `move`, which means it takes ownership of the string by *value*. The future that is returned by calling `closure()` returns a future which borrows a reference `&'call String` which lives until the future is dropped (e.g. it is `await`ed). #### Async fn trait family To support the lending capability of async closures, and to provide a first-class way to express higher-ranked async closures, we introduce the `AsyncFn*` family of traits. See the [corresponding section](https://rust-lang.github.io/rfcs/3668-async-closures.html#asyncfn) of the RFC. We stabilize naming `AsyncFn*` via the "parenthesized sugar" syntax that normal `Fn*` traits can be named. The `AsyncFn*` trait can be used anywhere a `Fn*` trait bound is allowed, such as: ```rust /// In return-position impl trait: fn closure() -> impl AsyncFn() { async || {} } /// In trait bounds: trait Foo<F>: Sized where F: AsyncFn() { fn new(f: F) -> Self; } /// in GATs: trait Gat { type AsyncHasher<T>: AsyncFn(T) -> i32; } ``` Other than using them in trait bounds, the definitions of these traits are not directly observable, but certain aspects of their behavior can be indirectly observed such as the fact that: * `AsyncFn::async_call` and `AsyncFnMut::async_call_mut` return a future which is *lending*, and therefore borrows the `&self` lifetime of the callee. ```rust fn by_ref_call(c: impl AsyncFn()) { let fut = c(); drop(c); // ^ Cannot drop `c` since it is borrowed by `fut`. } ``` * `AsyncFnOnce::async_call_once` returns a future that takes ownership of the callee. ```rust fn by_ref_call(c: impl AsyncFnOnce()) { let fut = c(); let _ = c(); // ^ Cannot call `c` since calling it takes ownership the callee. } ``` * All currently-stable callable types (i.e., closures, function items, function pointers, and `dyn Fn*` trait objects) automatically implement `AsyncFn*() -> T` if they implement `Fn*() -> Fut` for some output type `Fut`, and `Fut` implements `Future<Output = T>`. * This is to make sure that `AsyncFn*()` trait bounds have maximum compatibility with existing callable types which return futures, such as async function items and closures which return boxed futures. * For now, this only works currently for *concrete* callable types -- for example, a argument-position impl trait like `impl Fn() -> impl Future<Output = ()>` does not implement `AsyncFn()`, due to the fact that a `AsyncFn`-if-`Fn` blanket impl does not exist in reality. This may be relaxed in the future. Users can work around this by wrapping their type in an async closure and calling it. I expect this to not matter much in practice, as users are encouraged to write `AsyncFn` bounds directly. ```rust fn is_async_fn(_: impl AsyncFn(&str)) {} async fn async_fn_item(s: &str) { todo!() } is_async_fn(s); // ^^^ This works. fn generic(f: impl Fn() -> impl Future<Output = ()>) { is_async_fn(f); // ^^^ This does not work (yet). } ``` #### The by-move future When async closures are called with `AsyncFn`/`AsyncFnMut`, they return a coroutine that borrows from the closure. However, when they are called via `AsyncFnOnce`, we consume that closure, and cannot return a coroutine that borrows from data that is now dropped. To work around around this limitation, we synthesize a separate future type for calling the async closure via `AsyncFnOnce`. This future executes identically to the by-ref future returned from calling the async closure, except for the fact that it has a different set of captures, since we must *move* the captures from the parent async into the child future. #### Interactions between async closures and the `Fn*` family of traits Async closures always implement `FnOnce`, since they always can be called once. They may also implement `Fn` or `FnMut` if their body is compatible with the calling mode (i.e. if they do not mutate their captures, or they do not capture their captures, respectively) and if the future returned by the async closure is not *lending*. ```rust let id = String::new(); let mapped: Vec</* impl Future */> = [/* elements */] .into_iter() // `Iterator::map` takes an `impl FnMut` .map(async |element| { do_something(&id, element).await; }) .collect(); ``` See [the dev guide](https://rustc-dev-guide.rust-lang.org/coroutine-closures.html#follow-up-when-do-async-closures-implement-the-regular-fn-traits) for a detailed explanation for the situations where this may not be possible due to the lending nature of async closures. #### Other notable features of async closures shared with synchronous closures * Async closures are `Copy` and/or `Clone` if their captures are `Copy`/`Clone`. * Async closures do closure signature inference: If an async closure is passed to a function with a `AsyncFn` or `Fn` trait bound, we can eagerly infer the argument types of the closure. More details are provided in [the dev guide](https://rustc-dev-guide.rust-lang.org/coroutine-closures.html#closure-signature-inference). #### Lints This PR also stabilizes the `CLOSURE_RETURNING_ASYNC_BLOCK` lint as an `allow` lint. This lints on "old-style" async closures: ```rust #![warn(closure_returning_async_block)] let c = |x: &str| async {}; ``` We should encourage users to use `async || {}` where possible. This lint remains `allow` and may be refined in the future because it has a few false positives (namely, see: "Where do we expect rewriting `|| async {}` into `async || {}` to fail?") An alternative that could be made at the time of stabilization is to put this lint behind another gate, so we can decide to stabilize it later. ## What isn't stabilized (aka, potential future work) #### `async Fn*()` bound syntax We decided to stabilize async closures without the `async Fn*()` bound modifier syntax. The general direction of this syntax and how it fits is still being considered by T-lang (e.g. in [RFC 3710](rust-lang/rfcs#3710)). #### Naming the futures returned by async closures This stabilization PR does not provide a way of naming the futures returned by calling `AsyncFn*`. Exposing a stable way to refer to these futures is important for building async-closure-aware combinators, and will be an important future step. #### Return type notation-style bounds for async closures The RFC described an RTN-like syntax for putting bounds on the future returned by an async closure: ```rust async fn foo(x: F) -> Result<()> where F: AsyncFn(&str) -> Result<()>, // The future from calling `F` is `Send` and `'static`. F(..): Send + 'static, {} ``` This stabilization PR does not stabilize that syntax yet, which remains unimplemented (though will be soon). #### `dyn AsyncFn*()` `AsyncFn*` are not dyn-compatible yet. This will likely be implemented in the future along with the dyn-compatibility of async fn in trait, since the same issue (dealing with the future returned by a call) applies there. ## Tests Tests exist for this feature in [`tests/ui/async-await/async-closures`](/~https://github.com/rust-lang/rust/tree/5b542866400ad4a294f468cfa7e059d95c27a079/tests/ui/async-await/async-closures). <details> <summary>A selected set of tests:</summary> * Lending behavior of async closures * `tests/ui/async-await/async-closures/mutate.rs` * `tests/ui/async-await/async-closures/captures.rs` * `tests/ui/async-await/async-closures/precise-captures.rs` * `tests/ui/async-await/async-closures/no-borrow-from-env.rs` * Async closures may be higher-ranked * `tests/ui/async-await/async-closures/higher-ranked.rs` * `tests/ui/async-await/async-closures/higher-ranked-return.rs` * Async closures may implement `Fn*` traits * `tests/ui/async-await/async-closures/is-fn.rs` * `tests/ui/async-await/async-closures/implements-fnmut.rs` * Async closures may be cloned * `tests/ui/async-await/async-closures/clone-closure.rs` * Ownership of the upvars when `AsyncFnOnce` is called * `tests/ui/async-await/async-closures/drop.rs` * `tests/ui/async-await/async-closures/move-is-async-fn.rs` * `tests/ui/async-await/async-closures/force-move-due-to-inferred-kind.rs` * `tests/ui/async-await/async-closures/force-move-due-to-actually-fnonce.rs` * Closure signature inference * `tests/ui/async-await/async-closures/signature-deduction.rs` * `tests/ui/async-await/async-closures/sig-from-bare-fn.rs` * `tests/ui/async-await/async-closures/signature-inference-from-two-part-bound.rs` </details> ## Remaining bugs and open issues * rust-lang/rust#120694 tracks moving onto more general `LendingFn*` traits. No action needed, since it's not observable. * rust-lang/rust#124020 - Polymorphization ICE. Polymorphization needs to be heavily reworked. No action needed. * rust-lang/rust#127227 - Tracking reworking the way that rustdoc re-sugars bounds. * The part relevant to to `AsyncFn` is fixed by rust-lang/rust#132697. ## Where do we expect rewriting `|| async {}` into `async || {}` to fail? * Fn pointer coercions * Currently, it is not possible to coerce an async closure to an fn pointer like regular closures can be. This functionality may be implemented in the future. ```rust let x: fn() -> _ = async || {}; ``` * Argument capture * Like async functions, async closures always capture their input arguments. This is in contrast to something like `|t: T| async {}`, which doesn't capture `t` unless it is used in the async block. This may affect the `Send`-ness of the future or affect its outlives. ```rust fn needs_send_future(_: impl Fn(NotSendArg) -> Fut) where Fut: Future<Output = ()>, {} needs_send_future(async |_| {}); ``` ## History #### Important feature history - rust-lang/rust#51580 - rust-lang/rust#62292 - rust-lang/rust#120361 - rust-lang/rust#120712 - rust-lang/rust#121857 - rust-lang/rust#123660 - rust-lang/rust#125259 - rust-lang/rust#128506 - rust-lang/rust#127482 ## Acknowledgements Thanks to `@oli-obk` for reviewing the bulk of the work for this feature. Thanks to `@nikomatsakis` for his design blog posts which generated interest for this feature, `@traviscross` for feedback and additions to this stabilization report. All errors are my own. r? `@ghost`
This PR implements
async
/await
syntax forasync fn
in Rust 2015 andasync
closures andasync
blocks in Rust 2018 (tracking issue: #50547). Limitations: non-move
async closures with arguments are currently not supported, nor areasync fn
with multiple different input lifetimes. These limitations are not fundamental and will be removed in the future, however I'd like to go ahead and get this PR merged so we can start experimenting with this in combination with futures 0.3.Based on #51414.
cc @petrochenkov for parsing changes.
r? @eddyb