From f4dd25188f7f73d742628fc1869867e5442b7e8e Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Tue, 24 Apr 2018 23:18:22 +0200 Subject: [PATCH 01/10] Fix typo --- text/0000-async_await.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 03617302047..aaaf9c17f1b 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -651,4 +651,4 @@ async { The use of the `break` keyword instead of `return` could be beneficial to indicate that it applies to the async block and not its surrounding function. On the other hand this would introduce a difference to closures and async closures -which make use the `return` keyword. +which make use of the `return` keyword. From eea419125132397cddef88cc7b871f25e926ab0c Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Wed, 25 Apr 2018 06:27:30 +0200 Subject: [PATCH 02/10] Item -> Output --- text/0000-async_await.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index aaaf9c17f1b..9bcc133e5cc 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -196,7 +196,7 @@ state, which contains all of the arguments to this function. ### Trait bounds The anonymous return type implements `Future`, with the return type as its -`Item`. Polling it advances the state of the function, returning `Pending` +`Output`. Polling it advances the state of the function, returning `Pending` when it hits an `await` point, and `Ready` with the item when it hits a `return` point. Any attempt to poll it after it has already returned `Ready` once will panic. From 318ae128832b2a96193be7c3de6719aa2d50f163 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Wed, 25 Apr 2018 07:21:42 +0200 Subject: [PATCH 03/10] Rename Future to Async --- text/0000-async_await.md | 246 ++++++++++++++++++++------------------- 1 file changed, 125 insertions(+), 121 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 9bcc133e5cc..31e27678457 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -6,10 +6,14 @@ # Summary [summary]: #summary -Add async & await syntaxes to make it more ergonomic to write code manipulating -futures. +Add `async` & `await` syntaxes to make it more ergonomic to construct types that +implement `Async`. -This has a companion RFC to add a small futures API to libstd and libcore. +This has a companion RFC to add a small async API to libstd and libcore. + +Note: `Async` was previously called `Future`. However, due to to the many +subtle differeneces, it was decided to standardize it under a different, +slightly shorter name. # Motivation [motivation]: #motivation @@ -34,26 +38,26 @@ implemented using the futures interfaces. After gaining experience & user feedback with the futures-based ecosystem, we discovered certain ergonomics challenges. Using state which needs to be shared -across await points was extremely unergonomic - requiring either Arcs or join -chaining - and while combinators were often more ergonomic than manually -writing a future, they still often led to messy sets of nested and chained +across await points was extremely unergonomic - requiring either `Arc`s or +`join` chaining - and while combinators were often more ergonomic than manually +writing a `Future`, they still often led to messy sets of nested and chained callbacks. -Fortunately, the Future abstraction is well suited to use with a syntactic -sugar which has become common in many languages with async IO - the async and -await keywords. In brief, an asynchronous function returns a future, rather -than evaluating immediately when it is called. Inside the function, other -futures can be awaited using an await expression, which causes them to yield -control while the future is being polled. From a user's perspective, they can -use async/await as if it were synchronous code, and only need to annotate their -functions and calls. +Fortunately, the future abstraction (sometimes also called "promises") is well +suited to use with a syntactic sugar which has become common in many languages +with async IO - the `async` and `await` keywords. In brief, an asynchronous +function returns an `Async`, rather than evaluating immediately when it is +called. Inside the function, other `Async` types can be awaited using an await +expression, which causes them to yield control while the `Async` is being +polled. From a user's perspective, they can use `async`/`await` as if it were +synchronous code, and only need to annotate their functions and calls. -Async/await & futures can be a powerful abstraction for asynchronicity and +`async`/`await` & `Async` can be a powerful abstraction for asynchronicity and concurrency in general, and likely has applications outside of the asynchronous IO space. The use cases we've experience with today are generally tied to async IO, but by introducing first class syntax and libstd support we believe more -use cases for async & await will also flourish, that are not tied directly to -asynchronous IO. +use cases for `async` & `await` will also flourish, that are not tied directly +to asynchronous IO. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -71,12 +75,12 @@ async fn function(argument: &str) -> usize { Async functions work differently from normal functions. When an async function is called, it does not enter the body immediately. Instead, it evaluates to an -anonymous type which implements `Future`. As that future is polled, the +anonymous type which implements `Async`. As that `Async` is polled, the function is evaluated up to the next `await` or return point inside of it (see the await syntax section next). An async function is a kind of delayed computation - nothing in the body of the -function actually runs until you begin polling the future returned by the +function actually runs until you begin polling the `Async` returned by the function. For example: ```rust @@ -85,23 +89,23 @@ async fn print_async() { } fn main() { - let future = print_async(); + let my_async = print_async(); println!("Hello from main"); - futures::block_on(future); + futures::block_on(my_async); } ``` This will print `"Hello from main"` before printing `"Hello from print_async"`. An `async fn foo(args..) -> T` is a function of the type -`fn(args..) -> impl Future`. The return type is an anonymous type +`fn(args..) -> impl Async`. The return type is an anonymous type generated by the compiler. ### `async ||` closures In addition to functions, async can also be applied to closures. Like an async -function, an async closure has a return type of `impl Future`, rather -than `T`. When you call that closure, it returns a future immediately without +function, an async closure has a return type of `impl Async`, rather +than `T`. When you call that closure, it returns an `Async` immediately without evaluating any of the body (just like an async function). ```rust @@ -110,29 +114,29 @@ fn main() { println("Hello from async closure."); }; println!("Hello from main"); - let future = closure(); + let my_async = closure(); println!("Hello from main again"); - futures::block_on(future); + futures::block_on(my_async); } ``` This will print both "Hello from main" statements before printing "Hello from async closure." -`async` closures can be annotated with `move` to capture ownership of the +Async closures can be annotated with `move` to capture ownership of the variables they close over. ## `async` blocks -You can create a future directly as an expression using an `async` block: +You can create an `Async` directly as an expression using an async block: ```rust -let my_future = async { +let my_async = async { println!("Hello from an async block"); }; ``` -This form is almost equivalent to an immediately-invoked `async` closure. +This form is almost equivalent to an immediately-invoked async closure. That is: ```rust @@ -149,22 +153,22 @@ context like a closure or a loop). How the `?`-operator and early returns should work inside async blocks has not yet been established (see unresolved questions). -As with `async` closures, `async` blocks can be annotated with `move` to capture +As with async closures, async blocks can be annotated with `move` to capture ownership of the variables they close over. ## The `await!` compiler built-in A builtin called `await!` is added to the compiler. `await!` can be used to -"pause" the computation of the future, yielding control back to the caller. -`await!` takes any expression which implements `IntoFuture`, and evaluates to a -value of the item type that that future has. +"pause" the computation of the `Async`, yielding control back to the caller. +`await!` takes any expression which implements `IntoAsync`, and evaluates to a +value of the item type that `future` has. ```rust -// future: impl Future -let n = await!(future); +// my_async: impl Async +let n = await!(my_async); ``` -The expansion of await repeatedly calls `poll` on the future it receives, +The expansion of await repeatedly calls `poll` on the `Async` it receives, yielding control of the function when it returns `Poll::Pending` and eventually evaluating to the item value when it returns `Poll::Ready`. @@ -183,31 +187,31 @@ Both `async` and `await` become keywords, gated on the 2018 edition. ## Return type of `async` functions, closures, and blocks -The return type of an async function is a unique anonymous type generated by -the compiler, similar to the type of a closure. You can think of this type as -being like an enum, with one variant for every "yield point" of the function - -the beginning of it, the await expressions, and every return. Each variant -stores the state that is needed to be stored to resume control from that yield -point. +The return type of an async function is a unique anonymous type which implements +`Async` and is generated by the compiler, similar to the type of a closure. +You can think of this type as being like an enum, with one variant for every +"yield point" of the function - the beginning of it, the await expressions, +and every return. Each variant stores the state that is needed to be stored to +resume control from that yield point. When the function is called, this anonymous type is returned in its initial state, which contains all of the arguments to this function. ### Trait bounds -The anonymous return type implements `Future`, with the return type as its +The anonymous return type implements `Async`, with the return type as its `Output`. Polling it advances the state of the function, returning `Pending` when it hits an `await` point, and `Ready` with the item when it hits a `return` point. Any attempt to poll it after it has already returned `Ready` once will panic. The anonymous return type has a negative impl for the `Unpin` trait - that is -`impl !Unpin`. This is because the future could have internal references which +`impl !Unpin`. This is because the `Async` could have internal references which means it needs to never be moved. -## Lifetime capture in the anonymous future +## Lifetime capture in the anonymous `Async` -All of the input lifetimes to this function are captured in the future returned +All of the input lifetimes to this function are captured in the `Async` returned by the async function, because it stores all of the arguments to the function in its initial state (and possibly later states). That is, given a function like this: @@ -219,27 +223,27 @@ async fn foo(arg: &str) -> usize { ... } It has an equivalent type signature to this: ```rust -fn foo<'a>(arg: &'a str) -> impl Future + 'a { ... } +fn foo<'a>(arg: &'a str) -> impl Async + 'a { ... } ``` This is different from the default for `impl Trait`, which does not capture the lifetime. This is a big part of why the return type is `T` instead of `impl -Future`. +Async`. ### "Initialization" pattern -One pattern that sometimes occurs is that a future has an "initialization" step -which should be performed during its construction. This is useful when dealing -with data conversion and temporary borrows. Because the async function does not -begin evaluating until you poll it, and it captures the lifetimes of its -arguments, this pattern cannot be expressed directly with an `async fn`. +One pattern that sometimes occurs is that an `Async` has an "initialization" +step which should be performed during its construction. This is useful when +dealing with data conversion and temporary borrows. Because the async function +does not begin evaluating until you poll it, and it captures the lifetimes of +its arguments, this pattern cannot be expressed directly with an `async fn`. -One option is to write a function that returns `impl Future` using a closure +One option is to write a function that returns `impl Async` using a closure which is evaluated immediately: ```rust -// only arg1's lifetime is captured in the returned future -fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { +// only arg1's lifetime is captured in the returned Async +fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Async + 'a { // do some initialization using arg2 // closure which is evaluated immediately @@ -254,10 +258,10 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { The `await!` builtin expands roughly to this: ```rust -let mut future = IntoFuture::into_future($expression); -let mut pin = unsafe { Pin::new_unchecked(&mut future) }; +let mut my_async = IntoAsync::into_future($expression); +let mut pin = unsafe { Pin::new_unchecked(&mut my_async) }; loop { - match Future::poll(Pin::borrow(&mut pin), &mut ctx) { + match Async::poll(Pin::borrow(&mut pin), &mut ctx) { Poll::Ready(item) => break item, Poll::Pending => yield, } @@ -284,7 +288,7 @@ async move { # Drawbacks [drawbacks]: #drawbacks -Adding async & await syntax to Rust is a major change to the language - easily +Adding `async` & `await` syntax to Rust is a major change to the language - easily one of the most significant additions since 1.0. Though we have started with the smallest beachhead of features, in the long term the set of features it implies will grow as well (see the unresolved questions section). Such a @@ -292,11 +296,11 @@ significant addition mustn't be taken lightly, and only with strong motivation. We believe that an ergonomic asynchronous IO solution is essential to Rust's success as a language for writing high performance network services, one of our -goals for 2018. Async & await syntax based on the Future trait is the most +goals for 2018. Async & await syntax based on the Async trait is the most expedient & low risk path to achieving that goal in the near future. This RFC, along with its companion lib RFC, makes a much firmer commitment to -futures & async/await than we have previously as a project. If we decide to +Async & `async`/`await` than we have previously as a project. If we decide to reverse course after stabilizing these features, it will be quite costly. Adding an alternative mechanism for asynchronous programming would be more costly because this exists. However, given our experience with futures, we are @@ -315,27 +319,27 @@ appropriate section of the RFC. This section contains alternative design decisions which this RFC rejects (as opposed to those it merely postpones). -## The return type (`T` instead of `impl Future`) +## The return type (`T` instead of `impl Async`) The return type of an asynchronous function is a sort of complicated question. -There are two different perspectives on the return type of an async fn: the +There are two different perspectives on the return type of an `async fn`: the "interior" return type - the type that you return with the `return` keyword, and the "exterior" return type - the type that the function returns when you call it. -Most statically typed languages with async fns display the "outer" return type in the function signature. This RFC proposes instead to display the "inner" +Most statically typed languages with `async fn`s display the "outer" return type return type in the function signature. This has both advantages and disadvantages. ### The lifetime elision problem -As eluded to previously, the returned future captures all input lifetimes. By +As eluded to previously, the returned `Async` captures all input lifetimes. By default, `impl Trait` does not capture any lifetimes. To accurately reflect the outer return type, it would become necessary to eliminate lifetime elision: ```rust -async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Future + 'ret { +async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Async + 'ret { *x + *y } ``` @@ -344,7 +348,7 @@ This would be very unergonomic and make async both much less pleasant to use and much less easy to learn. This issue weighs heavily in the decision to prefer returning the interior type. -We could have it return `impl Future` but have lifetime capture work +We could have it return `impl Async` but have lifetime capture work differently for the return type of `async fn` than other functions; this seems worse than showing the interior type. @@ -362,7 +366,7 @@ return types other than `Task`. We do not have a compelling use case for this: call site. The motivation for the attribute variant was to support async methods in object-safe traits. This is a special case of supporting `impl Trait` in object-safe traits (probably by boxing the return type in the - object case), a feature we want separately from async fn. + object case), a feature we want separately from `async fn`. 3. It has been proposed that we support `async fn` which return streams. However, this mean that the semantics of the internal function would differ significantly between those which return futures and streams. As discussed @@ -385,10 +389,10 @@ you `return`. Rustdoc can handle async functions using the inner return type in a couple of ways to make them easier to understand. At minimum we should make sure to include the `async` annotation in the documentation, so that users who -understand async notation know that the function will return a future. We can -also perform other transformations, possibly optionally, to display the outer -signature of the function. Exactly how to handle API documentation for async -functions is left as an unresolved question. +understand `async` notation know that the function will return an `Async`. +We can also perform other transformations, possibly optionally, to display the +outer signature of the function. Exactly how to handle API documentation for +async functions is left as an unresolved question. ## Built-in syntax instead of using macros in generators @@ -409,8 +413,8 @@ faster stabilization. Generators can express a wider range of possibilities, and have a wider range of open questions - both syntactic and semantic. This does not even address the open questions of stabilizing more procedural macros. For this reason, we believe it is more expedient to stabilize the minimal -built-in async/await functionality than to attempt to stabilize generators and -proc macros. +built-in `async`/`await` functionality than to attempt to stabilize generators +and proc macros. ## `async` based on generators alone @@ -421,14 +425,14 @@ creating generators. In this design, we would write a generator like this: async fn foo(arg: Arg) -> Return yield Yield ``` -Both return and yield would be optional, default to `()`. An async fn that -yields `()` would implement `Future`, using a blanket impl. An async fn that +Both return and yield would be optional, default to `()`. An `async fn` that +yields `()` would implement `Async`, using a blanket impl. An `async fn` that returns `()` would implement `Iterator`. The problem with this approach is that does not ergonomically handle `Stream`s, which need to yield `Poll>`. It's unclear how `await` inside of an -async fn yielding something other than `()` (which would include streams) would -work. For this reason, the "matrix" approach in which we have independent +`async` fn yielding something other than `()` (which would include streams) +would work. For this reason, the "matrix" approach in which we have independent syntax for generator functions, async functions, and async generator functions, seems like a more promising approach. @@ -442,43 +446,43 @@ cases need to use a terminal async block, for example. An alternative would be to have async functions immediately evaluate up until their first `await`, preserving their state until then. The implementation of this would be quite complicated - they would need to have an additional yield -point within the `await`, prior to polling the future being awaited, +point within the `await`, prior to polling the `Async` being awaited, conditional on whether or not the await is the first await in the body of the -future. - -A fundamental difference between Rust's futures and those from other languages -is that Rust's futures do not do anything unless polled. The whole system is -built around this: for example, cancellation is dropping the future for -precisely this reason. In contrast, in other languages, calling an async fn -spins up a future that starts executing immediately. This difference carries -over to `async fn` and `async` blocks as well, where it's vital that the -resulting future be *actively polled* to make progress. Allowing for partial, +`Async`. + +A fundamental difference between Rust's `Async`s and those from other languages +is that Rust's `Async`s do not do anything unless polled. The whole system is +built around this: for example, cancellation is dropping the `Async` for +precisely this reason. In contrast, in other languages, calling an `async fn` +spins up an `Async` that starts executing immediately. This difference carries +over to `async fn` and async blocks as well, where it's vital that the +resulting `Async` be *actively polled* to make progress. Allowing for partial, eager execution is likely to lead to significant confusion and bugs. This is also complicated from a user perspective - when a portion of the body is evaluated depends on whether or not it appears before all `await` statements (which could possibly be macro generated). The use of a terminal async block provide a clearer mechanism for distinguishing between the -immediately evaluated and asynchronously evaluated portions of a future with an -initialization step. +immediately evaluated and asynchronously evaluated portions of an `Async` with +an initialization step. ## Using async/await instead of alternative asynchronicity systems -A final - and extreme - alternative would be to abandon futures and async/await -as the mechanism for async/await in Rust and to adopt a different paradigm. -Among those suggested are a generalized effects system, monads & do notation, -green-threading, and stack-full coroutines. +A final - and extreme - alternative would be to abandon `Async`s and +`async`/`await` as the mechanism for `async`/`await` in Rust and to adopt a +different paradigm. Among those suggested are a generalized effects system, +monads & do notation, green-threading, and stack-full coroutines. While it is hypothetically plausible that some generalization beyond -async/await could be supported by Rust, there has not enough research in this -area to support it in the near-term. Given our goals for 2018 - which emphasize -shipping - async/await syntax (a concept available widely in many languages -which interacts well with our existing async IO libraries) is the most logical -thing to implement at this stage in Rust's evolution. +`async`/`await` could be supported by Rust, there has not enough research in +this area to support it in the near-term. Given our goals for 2018 - which +emphasize shipping - `async`/`await` syntax (a concept available widely in many +languages which interacts well with our existing async IO libraries) is the most +logical thing to implement at this stage in Rust's evolution. ## Async blocks vs async closures -As noted in the main text, `async` blocks and `async` closures are closely +As noted in the main text, async blocks and async closures are closely related, and are roughly inter-expressible: ```rust @@ -497,9 +501,9 @@ We could consider having only one of the two constructs. However: such closures are often useful for higher-order constructs like constructing a service. -- There's a strong reason to have `async` blocks: The initialization pattern +- There's a strong reason to have async blocks: The initialization pattern mentioned in the RFC text, and the fact that it provides a more - direct/primitive way of constructing futures. + direct/primitive way of constructing `Async`s. The RFC proposes to include both constructs up front, since it seems inevitable that we will want both of them, but we can always reconsider this question @@ -508,8 +512,8 @@ before stabilization. # Prior art [prior-art]: #prior-art -There is a lot of precedence from other languages for async/await syntax as a -way of handling asynchronous operation - notable examples include C#, +There is a lot of precedence from other languages for `async`/`await` syntax as +a way of handling asynchronous operation - notable examples include C#, JavaScript, and Python. There are three paradigms for asynchronous programming which are dominant @@ -526,11 +530,11 @@ favorably with ownership and borrowing (unlike systems based on monads) and it enables us to have an entirely library-based asynchronicity model (unlike green-threading). -One way in which our handling of async/await differs from most other statically -typed languages (such as C#) is that we have chosen to show the "inner" return -type, rather than the outer return type. As discussed in the alternatives -section, Rust's specific context (lifetime elision, the lack of a need for -return type polymorphism here) make this deviation well-motivated. +One way in which our handling of `async`/`await` differs from most other +statically typed languages (such as C#) is that we have chosen to show the +"inner" return type, rather than the outer return type. As discussed in the +alternatives section, Rust's specific context (lifetime elision, the lack of a +need for return type polymorphism here) make this deviation well-motivated. # Unresolved questions [unresolved]: #unresolved-questions @@ -546,27 +550,27 @@ this is how to handle its precedence & whether or not to require delimiters of some kind. In particular, `await` has an interesting interaction with `?`. It is very -common to have a future which will evaluate to a `Result`, which the user will +common to have an `Async` which will evaluate to a `Result`, which the user will then want to apply `?` to. This implies that await should have a tighter precedence than `?`, so that the pattern will work how users wish it to. However, because it introduces a space, it doesn't look like this is the precedence you would get: ``` -await future? +await my_async? ``` There are a couple of possible solutions: 1. Require delimiters of some kind, maybe braces or parens or either, so that - it will look more like how you expect - `await { future }?` - this is rather - noisy. + it will look more like how you expect - `await { my_async }?` - this is + rather noisy. 2. Define the precedence as the obvious, if inconvenient precedence, requiring - users to write `(await future)?` - this seems very surprising for users. + users to write `(await my_async)?` - this seems very surprising for users. 3. Define the precedence as the inconvenient precedence - this seems equally surprising as the other precedence. 4. Introduce a special syntax to handle the multiple applications, such as - `await? future` - this seems very unusual in its own way. + `await? my_async` - this seems very unusual in its own way. This is left as an unresolved question to find another solution or decide which of these is least bad. @@ -590,7 +594,7 @@ small as possible). ## Generators and Streams In the future, we may also want to be able to define async functions that -evaluate to streams, rather than evaluating to futures. We propose to handle +evaluate to streams, rather than evaluating to `Async`s. We propose to handle this use case by way of generators. Generators can evaluate to a kind of iterator, while async generators can evaluate to a kind of stream. @@ -619,8 +623,8 @@ As proposed in this RFC, all async functions do not implement `Unpin`, making it unsafe to move them out of a `Pin`. This allows them to contain references across yield points. -We could also, with an annotation, typecheck an async function to confirm that it -does not contain any references across yield points, allowing it to implement +We could also, with an annotation, typecheck an async function to confirm that +it does not contain any references across yield points, allowing it to implement `Unpin`. The annotation to enable this is left unspecified for the time being. ## `?`-operator and control-flow constructs in async blocks @@ -636,7 +640,7 @@ let reader: AsyncRead = ...; async { let foo = await!(reader.read_to_end())?; Ok(foo.parse().unwrap_or(0)) -}: impl Future> +}: impl Async> ``` Also, it was discussed to allow the use of `break` to return early from From f3e14386d39b42767ca143d60a052cf868c447f5 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Wed, 25 Apr 2018 07:29:07 +0200 Subject: [PATCH 04/10] Fix consistency of `for await` --- text/0000-async_await.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 31e27678457..a1b76e7a82a 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -611,7 +611,7 @@ fn foo(mut x: i32) yield i32 { // Returns a stream of i32 async fn foo(io: &AsyncRead) yield i32 { - async for line in io.lines() { + for await line in io.lines() { yield line.unwrap().parse().unwrap(); } } From 95d0c9ae98ffd6d5c58940c7ebda80d45504c26d Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Wed, 25 Apr 2018 12:40:34 +0200 Subject: [PATCH 05/10] Fix some stuff --- text/0000-async_await.md | 69 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index a1b76e7a82a..7b5cc3e9221 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -12,8 +12,8 @@ implement `Async`. This has a companion RFC to add a small async API to libstd and libcore. Note: `Async` was previously called `Future`. However, due to to the many -subtle differeneces, it was decided to standardize it under a different, -slightly shorter name. +subtle differeneces that result from `Async` being pinning-aware, it was decided +to standardize it under a different name. # Motivation [motivation]: #motivation @@ -43,14 +43,15 @@ across await points was extremely unergonomic - requiring either `Arc`s or writing a `Future`, they still often led to messy sets of nested and chained callbacks. -Fortunately, the future abstraction (sometimes also called "promises") is well -suited to use with a syntactic sugar which has become common in many languages -with async IO - the `async` and `await` keywords. In brief, an asynchronous -function returns an `Async`, rather than evaluating immediately when it is -called. Inside the function, other `Async` types can be awaited using an await -expression, which causes them to yield control while the `Async` is being -polled. From a user's perspective, they can use `async`/`await` as if it were -synchronous code, and only need to annotate their functions and calls. +Fortunately, the future abstraction (in some languages also called "promises", +this RFC calls them `Async`s) is well suited to use with a syntactic sugar which +has become common in many languages with async IO - the `async` and `await` +keywords. In brief, the `async` keyword is used to define an asynchronous +function that returns an `Async`, rather than evaluating immediately, when it is +called. Inside the function, other `Async` types can be awaited using an `await` +expression, which causes them to yield control while the `Async` is being polled. +From a user's perspective, code that uses `async`/`await` feels very similar to +synchronous code. `async`/`await` & `Async` can be a powerful abstraction for asynchronicity and concurrency in general, and likely has applications outside of the asynchronous @@ -76,8 +77,8 @@ async fn function(argument: &str) -> usize { Async functions work differently from normal functions. When an async function is called, it does not enter the body immediately. Instead, it evaluates to an anonymous type which implements `Async`. As that `Async` is polled, the -function is evaluated up to the next `await` or return point inside of it (see -the await syntax section next). +function is evaluated up to the next `await` or `return` point inside of it (see +the `await` syntax section next). An async function is a kind of delayed computation - nothing in the body of the function actually runs until you begin polling the `Async` returned by the @@ -89,9 +90,9 @@ async fn print_async() { } fn main() { - let my_async = print_async(); + let op = print_async(); println!("Hello from main"); - futures::block_on(my_async); + futures::block_on(op); } ``` @@ -114,14 +115,14 @@ fn main() { println("Hello from async closure."); }; println!("Hello from main"); - let my_async = closure(); + let op = closure(); println!("Hello from main again"); - futures::block_on(my_async); + futures::block_on(op); } ``` This will print both "Hello from main" statements before printing "Hello from -async closure." +async closure". Async closures can be annotated with `move` to capture ownership of the variables they close over. @@ -131,7 +132,7 @@ variables they close over. You can create an `Async` directly as an expression using an async block: ```rust -let my_async = async { +let op = async { println!("Hello from an async block"); }; ``` @@ -161,11 +162,11 @@ ownership of the variables they close over. A builtin called `await!` is added to the compiler. `await!` can be used to "pause" the computation of the `Async`, yielding control back to the caller. `await!` takes any expression which implements `IntoAsync`, and evaluates to a -value of the item type that `future` has. +value of the item type that `op` has. ```rust -// my_async: impl Async -let n = await!(my_async); +// op: impl Async +let n = await!(op); ``` The expansion of await repeatedly calls `poll` on the `Async` it receives, @@ -201,8 +202,8 @@ state, which contains all of the arguments to this function. The anonymous return type implements `Async`, with the return type as its `Output`. Polling it advances the state of the function, returning `Pending` -when it hits an `await` point, and `Ready` with the item when it hits a -`return` point. Any attempt to poll it after it has already returned `Ready` +when it hits an await point, and `Ready` with the item when it hits a +return point. Any attempt to poll it after it has already returned `Ready` once will panic. The anonymous return type has a negative impl for the `Unpin` trait - that is @@ -258,10 +259,10 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Async + 'a { The `await!` builtin expands roughly to this: ```rust -let mut my_async = IntoAsync::into_future($expression); -let mut pin = unsafe { Pin::new_unchecked(&mut my_async) }; +let mut op = IntoAsync::into_async($expression); +let mut pin = unsafe { Pin::new_unchecked(&mut op) }; loop { - match Async::poll(Pin::borrow(&mut pin), &mut ctx) { + match Async::poll(Pin::borrow(&mut pin), &mut context) { Poll::Ready(item) => break item, Poll::Pending => yield, } @@ -296,11 +297,11 @@ significant addition mustn't be taken lightly, and only with strong motivation. We believe that an ergonomic asynchronous IO solution is essential to Rust's success as a language for writing high performance network services, one of our -goals for 2018. Async & await syntax based on the Async trait is the most +goals for 2018. Async & await syntax based on the `Async` trait is the most expedient & low risk path to achieving that goal in the near future. This RFC, along with its companion lib RFC, makes a much firmer commitment to -Async & `async`/`await` than we have previously as a project. If we decide to +`Async` & `async`/`await` than we have previously as a project. If we decide to reverse course after stabilizing these features, it will be quite costly. Adding an alternative mechanism for asynchronous programming would be more costly because this exists. However, given our experience with futures, we are @@ -519,13 +520,13 @@ JavaScript, and Python. There are three paradigms for asynchronous programming which are dominant today: -- Async and await notation. +- `async` and `await` notation. - An implicit concurrent runtime, often called "green-threading," such as communicating sequential processes (e.g. Go) or an actor model (e.g. Erlang). - Monadic transformations on lazily evaluated code, such as do notation (e.g. Haskell). -Async/await is the most compelling model for Rust because it interacts +`async`/`await` is the most compelling model for Rust because it interacts favorably with ownership and borrowing (unlike systems based on monads) and it enables us to have an entirely library-based asynchronicity model (unlike green-threading). @@ -557,20 +558,20 @@ However, because it introduces a space, it doesn't look like this is the precedence you would get: ``` -await my_async? +await op? ``` There are a couple of possible solutions: 1. Require delimiters of some kind, maybe braces or parens or either, so that - it will look more like how you expect - `await { my_async }?` - this is + it will look more like how you expect - `await { op }?` - this is rather noisy. 2. Define the precedence as the obvious, if inconvenient precedence, requiring - users to write `(await my_async)?` - this seems very surprising for users. + users to write `(await op)?` - this seems very surprising for users. 3. Define the precedence as the inconvenient precedence - this seems equally surprising as the other precedence. 4. Introduce a special syntax to handle the multiple applications, such as - `await? my_async` - this seems very unusual in its own way. + `await? op` - this seems very unusual in its own way. This is left as an unresolved question to find another solution or decide which of these is least bad. From ea55d5d810dac18025d0c18ff09aa74a46cb2aa8 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Fri, 4 May 2018 09:08:39 +0200 Subject: [PATCH 06/10] Rename Async back to Future --- text/0000-async_await.md | 150 +++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 77 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 7b5cc3e9221..ad1b5b4863d 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -7,13 +7,9 @@ [summary]: #summary Add `async` & `await` syntaxes to make it more ergonomic to construct types that -implement `Async`. +implement `Future`. -This has a companion RFC to add a small async API to libstd and libcore. - -Note: `Async` was previously called `Future`. However, due to to the many -subtle differeneces that result from `Async` being pinning-aware, it was decided -to standardize it under a different name. +This has a companion RFC to add a small futures API to libstd and libcore. # Motivation [motivation]: #motivation @@ -43,21 +39,21 @@ across await points was extremely unergonomic - requiring either `Arc`s or writing a `Future`, they still often led to messy sets of nested and chained callbacks. -Fortunately, the future abstraction (in some languages also called "promises", -this RFC calls them `Async`s) is well suited to use with a syntactic sugar which -has become common in many languages with async IO - the `async` and `await` -keywords. In brief, the `async` keyword is used to define an asynchronous -function that returns an `Async`, rather than evaluating immediately, when it is -called. Inside the function, other `Async` types can be awaited using an `await` -expression, which causes them to yield control while the `Async` is being polled. -From a user's perspective, code that uses `async`/`await` feels very similar to -synchronous code. - -`async`/`await` & `Async` can be a powerful abstraction for asynchronicity and +Fortunately, the `Future` abstraction (in some languages also called "promises") +is well suited to use with a syntactic sugar which has become common in many +languages with async IO - the `async` and `await` keywords. +In brief, the `async` keyword is used to define an asynchronous function that +returns a `Future`, rather than evaluating immediately, when it is called. +Inside the function, other `Future` types can be awaited using an `await` +expression, which causes them to yield control while the `Future` is being +polled. From a user's perspective, code that uses `async`/`await` feels very +similar to synchronous code. + +`async`/`await` & `Future` can be a powerful abstraction for asynchronicity and concurrency in general, and likely has applications outside of the asynchronous IO space. The use cases we've experience with today are generally tied to async IO, but by introducing first class syntax and libstd support we believe more -use cases for `async` & `await` will also flourish, that are not tied directly +use cases for `async` & `await` will also flourish, that are not tied directly to asynchronous IO. # Guide-level explanation @@ -74,14 +70,14 @@ async fn function(argument: &str) -> usize { } ``` -Async functions work differently from normal functions. When an async function +Async functions work differently than normal functions. When an async function is called, it does not enter the body immediately. Instead, it evaluates to an -anonymous type which implements `Async`. As that `Async` is polled, the +anonymous type which implements `Future`. As that `Future` is polled, the function is evaluated up to the next `await` or `return` point inside of it (see the `await` syntax section next). An async function is a kind of delayed computation - nothing in the body of the -function actually runs until you begin polling the `Async` returned by the +function actually runs until you begin polling the `Future` returned by the function. For example: ```rust @@ -90,23 +86,23 @@ async fn print_async() { } fn main() { - let op = print_async(); + let future = print_async(); println!("Hello from main"); - futures::block_on(op); + futures::block_on(future); } ``` This will print `"Hello from main"` before printing `"Hello from print_async"`. An `async fn foo(args..) -> T` is a function of the type -`fn(args..) -> impl Async`. The return type is an anonymous type +`fn(args..) -> impl Future`. The return type is an anonymous type generated by the compiler. ### `async ||` closures In addition to functions, async can also be applied to closures. Like an async -function, an async closure has a return type of `impl Async`, rather -than `T`. When you call that closure, it returns an `Async` immediately without +function, an async closure has a return type of `impl Future`, rather +than `T`. When you call that closure, it returns a `Future` immediately without evaluating any of the body (just like an async function). ```rust @@ -115,9 +111,9 @@ fn main() { println("Hello from async closure."); }; println!("Hello from main"); - let op = closure(); + let future = closure(); println!("Hello from main again"); - futures::block_on(op); + futures::block_on(future); } ``` @@ -129,10 +125,10 @@ variables they close over. ## `async` blocks -You can create an `Async` directly as an expression using an async block: +You can create a `Future` directly as an expression using an async block: ```rust -let op = async { +let future = async { println!("Hello from an async block"); }; ``` @@ -160,16 +156,16 @@ ownership of the variables they close over. ## The `await!` compiler built-in A builtin called `await!` is added to the compiler. `await!` can be used to -"pause" the computation of the `Async`, yielding control back to the caller. -`await!` takes any expression which implements `IntoAsync`, and evaluates to a -value of the item type that `op` has. +"pause" the computation of the `Future`, yielding control back to the caller. +`await!` takes any expression which implements `IntoFuture`, and evaluates to a +value of the item type that `Future` has. ```rust -// op: impl Async -let n = await!(op); +// future: impl Future +let n = await!(future); ``` -The expansion of await repeatedly calls `poll` on the `Async` it receives, +The expansion of await repeatedly calls `poll` on the `Future` it receives, yielding control of the function when it returns `Poll::Pending` and eventually evaluating to the item value when it returns `Poll::Ready`. @@ -189,7 +185,7 @@ Both `async` and `await` become keywords, gated on the 2018 edition. ## Return type of `async` functions, closures, and blocks The return type of an async function is a unique anonymous type which implements -`Async` and is generated by the compiler, similar to the type of a closure. +`Future` and is generated by the compiler, similar to the type of a closure. You can think of this type as being like an enum, with one variant for every "yield point" of the function - the beginning of it, the await expressions, and every return. Each variant stores the state that is needed to be stored to @@ -200,19 +196,19 @@ state, which contains all of the arguments to this function. ### Trait bounds -The anonymous return type implements `Async`, with the return type as its +The anonymous return type implements `Future`, with the return type as its `Output`. Polling it advances the state of the function, returning `Pending` when it hits an await point, and `Ready` with the item when it hits a return point. Any attempt to poll it after it has already returned `Ready` once will panic. The anonymous return type has a negative impl for the `Unpin` trait - that is -`impl !Unpin`. This is because the `Async` could have internal references which +`impl !Unpin`. This is because the `Future` could have internal references which means it needs to never be moved. -## Lifetime capture in the anonymous `Async` +## Lifetime capture in the anonymous `Future` -All of the input lifetimes to this function are captured in the `Async` returned +All of the input lifetimes to this function are captured in the `Future` returned by the async function, because it stores all of the arguments to the function in its initial state (and possibly later states). That is, given a function like this: @@ -224,27 +220,27 @@ async fn foo(arg: &str) -> usize { ... } It has an equivalent type signature to this: ```rust -fn foo<'a>(arg: &'a str) -> impl Async + 'a { ... } +fn foo<'a>(arg: &'a str) -> impl Future + 'a { ... } ``` This is different from the default for `impl Trait`, which does not capture the lifetime. This is a big part of why the return type is `T` instead of `impl -Async`. +Future`. ### "Initialization" pattern -One pattern that sometimes occurs is that an `Async` has an "initialization" +One pattern that sometimes occurs is that a `Future` has an "initialization" step which should be performed during its construction. This is useful when dealing with data conversion and temporary borrows. Because the async function does not begin evaluating until you poll it, and it captures the lifetimes of its arguments, this pattern cannot be expressed directly with an `async fn`. -One option is to write a function that returns `impl Async` using a closure +One option is to write a function that returns `impl Future` using a closure which is evaluated immediately: ```rust -// only arg1's lifetime is captured in the returned Async -fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Async + 'a { +// only arg1's lifetime is captured in the returned Future +fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { // do some initialization using arg2 // closure which is evaluated immediately @@ -259,10 +255,10 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Async + 'a { The `await!` builtin expands roughly to this: ```rust -let mut op = IntoAsync::into_async($expression); -let mut pin = unsafe { Pin::new_unchecked(&mut op) }; +let mut future = IntoFuture::into_future($expression); +let mut pin = unsafe { Pin::new_unchecked(&mut future) }; loop { - match Async::poll(Pin::borrow(&mut pin), &mut context) { + match Future::poll(Pin::borrow(&mut pin), &mut context) { Poll::Ready(item) => break item, Poll::Pending => yield, } @@ -297,11 +293,11 @@ significant addition mustn't be taken lightly, and only with strong motivation. We believe that an ergonomic asynchronous IO solution is essential to Rust's success as a language for writing high performance network services, one of our -goals for 2018. Async & await syntax based on the `Async` trait is the most +goals for 2018. `async` & `await` syntax based on the `Future` trait is the most expedient & low risk path to achieving that goal in the near future. This RFC, along with its companion lib RFC, makes a much firmer commitment to -`Async` & `async`/`await` than we have previously as a project. If we decide to +`Future` & `async`/`await` than we have previously as a project. If we decide to reverse course after stabilizing these features, it will be quite costly. Adding an alternative mechanism for asynchronous programming would be more costly because this exists. However, given our experience with futures, we are @@ -320,7 +316,7 @@ appropriate section of the RFC. This section contains alternative design decisions which this RFC rejects (as opposed to those it merely postpones). -## The return type (`T` instead of `impl Async`) +## The return type (`T` instead of `impl Future`) The return type of an asynchronous function is a sort of complicated question. There are two different perspectives on the return type of an `async fn`: the @@ -335,12 +331,12 @@ disadvantages. ### The lifetime elision problem -As eluded to previously, the returned `Async` captures all input lifetimes. By +As eluded to previously, the returned `Future` captures all input lifetimes. By default, `impl Trait` does not capture any lifetimes. To accurately reflect the outer return type, it would become necessary to eliminate lifetime elision: ```rust -async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Async + 'ret { +async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Future + 'ret { *x + *y } ``` @@ -349,7 +345,7 @@ This would be very unergonomic and make async both much less pleasant to use and much less easy to learn. This issue weighs heavily in the decision to prefer returning the interior type. -We could have it return `impl Async` but have lifetime capture work +We could have it return `impl Future` but have lifetime capture work differently for the return type of `async fn` than other functions; this seems worse than showing the interior type. @@ -390,7 +386,7 @@ you `return`. Rustdoc can handle async functions using the inner return type in a couple of ways to make them easier to understand. At minimum we should make sure to include the `async` annotation in the documentation, so that users who -understand `async` notation know that the function will return an `Async`. +understand `async` notation know that the function will return a `Future`. We can also perform other transformations, possibly optionally, to display the outer signature of the function. Exactly how to handle API documentation for async functions is left as an unresolved question. @@ -427,7 +423,7 @@ async fn foo(arg: Arg) -> Return yield Yield ``` Both return and yield would be optional, default to `()`. An `async fn` that -yields `()` would implement `Async`, using a blanket impl. An `async fn` that +yields `()` would implement `Future`, using a blanket impl. An `async fn` that returns `()` would implement `Iterator`. The problem with this approach is that does not ergonomically handle `Stream`s, @@ -447,36 +443,36 @@ cases need to use a terminal async block, for example. An alternative would be to have async functions immediately evaluate up until their first `await`, preserving their state until then. The implementation of this would be quite complicated - they would need to have an additional yield -point within the `await`, prior to polling the `Async` being awaited, +point within the `await`, prior to polling the `Future` being awaited, conditional on whether or not the await is the first await in the body of the -`Async`. +`Future`. -A fundamental difference between Rust's `Async`s and those from other languages -is that Rust's `Async`s do not do anything unless polled. The whole system is -built around this: for example, cancellation is dropping the `Async` for +A fundamental difference between Rust's `Future`s and those from other languages +is that Rust's `Future`s do not do anything unless polled. The whole system is +built around this: for example, cancellation is dropping the `Future` for precisely this reason. In contrast, in other languages, calling an `async fn` -spins up an `Async` that starts executing immediately. This difference carries +spins up a `Future` that starts executing immediately. This difference carries over to `async fn` and async blocks as well, where it's vital that the -resulting `Async` be *actively polled* to make progress. Allowing for partial, +resulting `Future` be *actively polled* to make progress. Allowing for partial, eager execution is likely to lead to significant confusion and bugs. This is also complicated from a user perspective - when a portion of the body is evaluated depends on whether or not it appears before all `await` statements (which could possibly be macro generated). The use of a terminal async block provide a clearer mechanism for distinguishing between the -immediately evaluated and asynchronously evaluated portions of an `Async` with +immediately evaluated and asynchronously evaluated portions of a `Future` with an initialization step. ## Using async/await instead of alternative asynchronicity systems -A final - and extreme - alternative would be to abandon `Async`s and +A final - and extreme - alternative would be to abandon `Future`s and `async`/`await` as the mechanism for `async`/`await` in Rust and to adopt a different paradigm. Among those suggested are a generalized effects system, monads & do notation, green-threading, and stack-full coroutines. While it is hypothetically plausible that some generalization beyond -`async`/`await` could be supported by Rust, there has not enough research in -this area to support it in the near-term. Given our goals for 2018 - which +`async`/`await` could be supported by Rust, there has not been enough research +in this area to support it in the near-term. Given our goals for 2018 - which emphasize shipping - `async`/`await` syntax (a concept available widely in many languages which interacts well with our existing async IO libraries) is the most logical thing to implement at this stage in Rust's evolution. @@ -504,7 +500,7 @@ We could consider having only one of the two constructs. However: - There's a strong reason to have async blocks: The initialization pattern mentioned in the RFC text, and the fact that it provides a more - direct/primitive way of constructing `Async`s. + direct/primitive way of constructing `Future`s. The RFC proposes to include both constructs up front, since it seems inevitable that we will want both of them, but we can always reconsider this question @@ -551,27 +547,27 @@ this is how to handle its precedence & whether or not to require delimiters of some kind. In particular, `await` has an interesting interaction with `?`. It is very -common to have an `Async` which will evaluate to a `Result`, which the user will +common to have a `Future` which will evaluate to a `Result`, which the user will then want to apply `?` to. This implies that await should have a tighter precedence than `?`, so that the pattern will work how users wish it to. However, because it introduces a space, it doesn't look like this is the precedence you would get: ``` -await op? +await future? ``` There are a couple of possible solutions: 1. Require delimiters of some kind, maybe braces or parens or either, so that - it will look more like how you expect - `await { op }?` - this is + it will look more like how you expect - `await { future }?` - this is rather noisy. 2. Define the precedence as the obvious, if inconvenient precedence, requiring - users to write `(await op)?` - this seems very surprising for users. + users to write `(await future)?` - this seems very surprising for users. 3. Define the precedence as the inconvenient precedence - this seems equally surprising as the other precedence. 4. Introduce a special syntax to handle the multiple applications, such as - `await? op` - this seems very unusual in its own way. + `await? future` - this seems very unusual in its own way. This is left as an unresolved question to find another solution or decide which of these is least bad. @@ -595,7 +591,7 @@ small as possible). ## Generators and Streams In the future, we may also want to be able to define async functions that -evaluate to streams, rather than evaluating to `Async`s. We propose to handle +evaluate to streams, rather than evaluating to `Future`s. We propose to handle this use case by way of generators. Generators can evaluate to a kind of iterator, while async generators can evaluate to a kind of stream. @@ -641,7 +637,7 @@ let reader: AsyncRead = ...; async { let foo = await!(reader.read_to_end())?; Ok(foo.parse().unwrap_or(0)) -}: impl Async> +}: impl Future> ``` Also, it was discussed to allow the use of `break` to return early from From 46e58aee722da3f660e3994a5d194a4bebcd1bf6 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Fri, 4 May 2018 13:23:35 +0200 Subject: [PATCH 07/10] Consistently highlight async and await keywords --- text/0000-async_await.md | 141 ++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index ad1b5b4863d..6a9d0ce3590 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -34,7 +34,7 @@ implemented using the futures interfaces. After gaining experience & user feedback with the futures-based ecosystem, we discovered certain ergonomics challenges. Using state which needs to be shared -across await points was extremely unergonomic - requiring either `Arc`s or +across `await` points was extremely unergonomic - requiring either `Arc`s or `join` chaining - and while combinators were often more ergonomic than manually writing a `Future`, they still often led to messy sets of nested and chained callbacks. @@ -59,9 +59,9 @@ to asynchronous IO. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -## Async functions +## `async` functions -Functions can be annotated with the `async` keyword, making them "async +Functions can be annotated with the `async` keyword, making them "`async` functions": ```rust @@ -70,13 +70,13 @@ async fn function(argument: &str) -> usize { } ``` -Async functions work differently than normal functions. When an async function -is called, it does not enter the body immediately. Instead, it evaluates to an -anonymous type which implements `Future`. As that `Future` is polled, the -function is evaluated up to the next `await` or `return` point inside of it (see -the `await` syntax section next). +`async` functions work differently than normal functions. When an `async` +function is called, it does not enter the body immediately. Instead, it +evaluates to an anonymous type which implements `Future`. As that `Future` is +polled, the function is evaluated up to the next `await` or `return` point +inside of it (see the `await` syntax section next). -An async function is a kind of delayed computation - nothing in the body of the +An `async` function is a kind of delayed computation - nothing in the body of the function actually runs until you begin polling the `Future` returned by the function. For example: @@ -100,10 +100,11 @@ generated by the compiler. ### `async ||` closures -In addition to functions, async can also be applied to closures. Like an async -function, an async closure has a return type of `impl Future`, rather -than `T`. When you call that closure, it returns a `Future` immediately without -evaluating any of the body (just like an async function). +In addition to functions, `async` can also be applied to closures. Like an +`async` function, an `async` closure has a return type of +`impl Future`, rather than `T`. When you call that closure, it +returns a `Future` immediately without evaluating any of the body (just like +an `async` function). ```rust fn main() { @@ -118,14 +119,14 @@ fn main() { ``` This will print both "Hello from main" statements before printing "Hello from -async closure". +`async` closure". -Async closures can be annotated with `move` to capture ownership of the +`async` closures can be annotated with `move` to capture ownership of the variables they close over. ## `async` blocks -You can create a `Future` directly as an expression using an async block: +You can create a `Future` directly as an expression using an `async` block: ```rust let future = async { @@ -133,7 +134,7 @@ let future = async { }; ``` -This form is almost equivalent to an immediately-invoked async closure. +This form is almost equivalent to an immediately-invoked `async` closure. That is: ```rust @@ -147,10 +148,10 @@ async { /* body */ } except that control-flow constructs like `return`, `break` and `continue` are not allowed within `body` (unless they appear within a fresh control-flow context like a closure or a loop). How the `?`-operator and early returns -should work inside async blocks has not yet been established (see unresolved +should work inside `async` blocks has not yet been established (see unresolved questions). -As with async closures, async blocks can be annotated with `move` to capture +As with `async` closures, `async` blocks can be annotated with `move` to capture ownership of the variables they close over. ## The `await!` compiler built-in @@ -165,11 +166,11 @@ value of the item type that `Future` has. let n = await!(future); ``` -The expansion of await repeatedly calls `poll` on the `Future` it receives, +The expansion of `await` repeatedly calls `poll` on the `Future` it receives, yielding control of the function when it returns `Poll::Pending` and eventually evaluating to the item value when it returns `Poll::Ready`. -`await!` can only be used inside of an async function, closure, or block. +`await!` can only be used inside of an `async` function, closure, or block. Using it outside of that context is an error. (`await!` is a compiler built-in to leave space for deciding its exact syntax @@ -184,10 +185,10 @@ Both `async` and `await` become keywords, gated on the 2018 edition. ## Return type of `async` functions, closures, and blocks -The return type of an async function is a unique anonymous type which implements +The return type of an `async` function is a unique anonymous type which implements `Future` and is generated by the compiler, similar to the type of a closure. You can think of this type as being like an enum, with one variant for every -"yield point" of the function - the beginning of it, the await expressions, +"yield point" of the function - the beginning of it, the `await` expressions, and every return. Each variant stores the state that is needed to be stored to resume control from that yield point. @@ -198,7 +199,7 @@ state, which contains all of the arguments to this function. The anonymous return type implements `Future`, with the return type as its `Output`. Polling it advances the state of the function, returning `Pending` -when it hits an await point, and `Ready` with the item when it hits a +when it hits an `await` point, and `Ready` with the item when it hits a return point. Any attempt to poll it after it has already returned `Ready` once will panic. @@ -209,7 +210,7 @@ means it needs to never be moved. ## Lifetime capture in the anonymous `Future` All of the input lifetimes to this function are captured in the `Future` returned -by the async function, because it stores all of the arguments to the function +by the `async` function, because it stores all of the arguments to the function in its initial state (and possibly later states). That is, given a function like this: @@ -231,7 +232,7 @@ Future`. One pattern that sometimes occurs is that a `Future` has an "initialization" step which should be performed during its construction. This is useful when -dealing with data conversion and temporary borrows. Because the async function +dealing with data conversion and temporary borrows. Because the `async` function does not begin evaluating until you poll it, and it captures the lifetimes of its arguments, this pattern cannot be expressed directly with an `async fn`. @@ -250,7 +251,7 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { } ``` -## The expansion of await +## The expansion of `await` The `await!` builtin expands roughly to this: @@ -271,7 +272,7 @@ is a compiler builtin instead of an actual macro. ## The order of `async` and `move` -Async closures and blocks can be annotated with `move` to capture ownership of +`async` closures and blocks can be annotated with `move` to capture ownership of the variables they close over. The order of the keywords is fixed to `async move`. Permitting only one ordering avoids confusion about whether it is significant for the meaning. @@ -300,13 +301,13 @@ This RFC, along with its companion lib RFC, makes a much firmer commitment to `Future` & `async`/`await` than we have previously as a project. If we decide to reverse course after stabilizing these features, it will be quite costly. Adding an alternative mechanism for asynchronous programming would be more -costly because this exists. However, given our experience with futures, we are +costly because this exists. However, given our experience with s, we are confident that this is the correct path forward. There are drawbacks to several of the smaller decisions we have made as well. There is a trade off between using the "inner" return type and the "outer" -return type, for example. We could have a different evaluation model for async -functions in which they are evaluated immediately up to the first await point. +return type, for example. We could have a different evaluation model for `async` +functions in which they are evaluated immediately up to the first `await` point. The decisions we made on each of these questions are justified in the appropriate section of the RFC. @@ -341,7 +342,7 @@ async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Future` -(their "outer type") was that they wanted to have async functions which could +(their "outer type") was that they wanted to have `async` functions which could return types other than `Task`. We do not have a compelling use case for this: 1. In the 0.2 branch of futures, there is a distinction between `Future` and `StableFuture`. However, this distinction is artificial and only because object-safe custom self-types are not available on stable yet. 2. The current `#[async]` macro has a `(boxed)` variant. We would prefer to - have async functions always be unboxed and only box them explicitly at the - call site. The motivation for the attribute variant was to support async + have `async` functions always be unboxed and only box them explicitly at the + call site. The motivation for the attribute variant was to support `async` methods in object-safe traits. This is a special case of supporting `impl Trait` in object-safe traits (probably by boxing the return type in the object case), a feature we want separately from `async fn`. @@ -368,7 +369,7 @@ return types other than `Task`. We do not have a compelling use case for this: However, this mean that the semantics of the internal function would differ significantly between those which return futures and streams. As discussed in the unresolved questions section, a solution based on generators and - async generators seems more promising. + `async` generators seems more promising. For these reasons, we don't think there's a strong argument from polymorphism to return the outer type. @@ -379,31 +380,31 @@ There are arguments from learnability in favor of both the outer and inner return type. One of the most compelling arguments in favor of the outer return type is documentation: when you read automatically generated API docs, you will definitely see what you get as the caller. In contrast, it can be easier to -understand how to write an async function using the inner return type, because +understand how to write an `async` function using the inner return type, because of the correspondence between the return type and the type of the expressions you `return`. -Rustdoc can handle async functions using the inner return type in a couple of +Rustdoc can handle `async` functions using the inner return type in a couple of ways to make them easier to understand. At minimum we should make sure to include the `async` annotation in the documentation, so that users who understand `async` notation know that the function will return a `Future`. We can also perform other transformations, possibly optionally, to display the outer signature of the function. Exactly how to handle API documentation for -async functions is left as an unresolved question. +`async` functions is left as an unresolved question. ## Built-in syntax instead of using macros in generators Another alternative is to focus on stabilizing procedural macros and -generators, rather than introducing built-in syntax for async functions. An -async function can be modeled as a generator which yields `()`. +generators, rather than introducing built-in syntax for `async` functions. An +`async` function can be modeled as a generator which yields `()`. -In the long run, we believe we will want dedicated syntax for async functions, +In the long run, we believe we will want dedicated syntax for `async` functions, because it is more ergonomic & the use case is compelling and significant enough to justify it (similar to - for example - having built in for loops and if statements rather than having macros which compile to loops and match statements). Given that, the only question is whether or not we could have a more expedited stability by using generators for the time being than by -introducing async functions now. +introducing `async` functions now. It seems unlikely that using macros which expand to generators will result in a faster stabilization. Generators can express a wider range of possibilities, @@ -415,7 +416,7 @@ and proc macros. ## `async` based on generators alone -Another alternative design would be to have async functions *be* the syntax for +Another alternative design would be to have `async` functions *be* the syntax for creating generators. In this design, we would write a generator like this: ```rust @@ -430,21 +431,21 @@ The problem with this approach is that does not ergonomically handle `Stream`s, which need to yield `Poll>`. It's unclear how `await` inside of an `async` fn yielding something other than `()` (which would include streams) would work. For this reason, the "matrix" approach in which we have independent -syntax for generator functions, async functions, and async generator functions, -seems like a more promising approach. +syntax for generator functions, `async` functions, and `async` generator +functions, seems like a more promising approach. -## "Hot async functions" +## "Hot `async` functions" -As proposed by this RFC, all async functions return immediately, without +As proposed by this RFC, all `async` functions return immediately, without evaluating their bodies at all. As discussed above, this is not convenient for use cases in which you have an immediate "initialization" step - those use -cases need to use a terminal async block, for example. +cases need to use a terminal `async` block, for example. -An alternative would be to have async functions immediately evaluate up until +An alternative would be to have `async` functions immediately evaluate up until their first `await`, preserving their state until then. The implementation of this would be quite complicated - they would need to have an additional yield point within the `await`, prior to polling the `Future` being awaited, -conditional on whether or not the await is the first await in the body of the +conditional on whether or not the await is the first `await` in the body of the `Future`. A fundamental difference between Rust's `Future`s and those from other languages @@ -452,18 +453,18 @@ is that Rust's `Future`s do not do anything unless polled. The whole system is built around this: for example, cancellation is dropping the `Future` for precisely this reason. In contrast, in other languages, calling an `async fn` spins up a `Future` that starts executing immediately. This difference carries -over to `async fn` and async blocks as well, where it's vital that the +over to `async fn` and `async` blocks as well, where it's vital that the resulting `Future` be *actively polled* to make progress. Allowing for partial, eager execution is likely to lead to significant confusion and bugs. This is also complicated from a user perspective - when a portion of the body is evaluated depends on whether or not it appears before all `await` statements (which could possibly be macro generated). The use of a terminal -async block provide a clearer mechanism for distinguishing between the +`async` block provide a clearer mechanism for distinguishing between the immediately evaluated and asynchronously evaluated portions of a `Future` with an initialization step. -## Using async/await instead of alternative asynchronicity systems +## Using `async`/`await` instead of alternative asynchronicity systems A final - and extreme - alternative would be to abandon `Future`s and `async`/`await` as the mechanism for `async`/`await` in Rust and to adopt a @@ -477,9 +478,9 @@ emphasize shipping - `async`/`await` syntax (a concept available widely in many languages which interacts well with our existing async IO libraries) is the most logical thing to implement at this stage in Rust's evolution. -## Async blocks vs async closures +## `async` blocks vs `async` closures -As noted in the main text, async blocks and async closures are closely +As noted in the main text, `async` blocks and `async` closures are closely related, and are roughly inter-expressible: ```rust @@ -498,7 +499,7 @@ We could consider having only one of the two constructs. However: such closures are often useful for higher-order constructs like constructing a service. -- There's a strong reason to have async blocks: The initialization pattern +- There's a strong reason to have `async` blocks: The initialization pattern mentioned in the RFC text, and the fact that it provides a more direct/primitive way of constructing `Future`s. @@ -548,7 +549,7 @@ some kind. In particular, `await` has an interesting interaction with `?`. It is very common to have a `Future` which will evaluate to a `Result`, which the user will -then want to apply `?` to. This implies that await should have a tighter +then want to apply `?` to. This implies that `await` should have a tighter precedence than `?`, so that the pattern will work how users wish it to. However, because it introduces a space, it doesn't look like this is the precedence you would get: @@ -590,10 +591,10 @@ small as possible). ## Generators and Streams -In the future, we may also want to be able to define async functions that +In the future, we may also want to be able to define `async` functions that evaluate to streams, rather than evaluating to `Future`s. We propose to handle this use case by way of generators. Generators can evaluate to a kind of -iterator, while async generators can evaluate to a kind of stream. +iterator, while `async` generators can evaluate to a kind of stream. For example (using syntax which could change); @@ -614,22 +615,22 @@ async fn foo(io: &AsyncRead) yield i32 { } ``` -## Async functions which implement `Unpin` +## `async` functions which implement `Unpin` -As proposed in this RFC, all async functions do not implement `Unpin`, making +As proposed in this RFC, all `async` functions do not implement `Unpin`, making it unsafe to move them out of a `Pin`. This allows them to contain references across yield points. -We could also, with an annotation, typecheck an async function to confirm that +We could also, with an annotation, typecheck an `async` function to confirm that it does not contain any references across yield points, allowing it to implement `Unpin`. The annotation to enable this is left unspecified for the time being. -## `?`-operator and control-flow constructs in async blocks +## `?`-operator and control-flow constructs in `async` blocks This RFC does not propose how the `?`-operator and control-flow constructs like -`return`, `break` and `continue` should work inside async blocks. +`return`, `break` and `continue` should work inside `async` blocks. -It was discussed that async blocks should act as a boundary for the +It was discussed that `async` blocks should act as a boundary for the `?`-operator. This would make them suitable for fallible IO: ```rust @@ -641,7 +642,7 @@ async { ``` Also, it was discussed to allow the use of `break` to return early from -an async block: +an `async` block: ```rust async { @@ -650,6 +651,6 @@ async { ``` The use of the `break` keyword instead of `return` could be beneficial to -indicate that it applies to the async block and not its surrounding function. On -the other hand this would introduce a difference to closures and async closures -which make use of the `return` keyword. +indicate that it applies to the `async` block and not its surrounding function. +On the other hand this would introduce a difference to closures and `async` +closures which make use of the `return` keyword. From 26f6bfed317c36ec3a038b2e861668a24b6f7310 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Fri, 4 May 2018 13:29:05 +0200 Subject: [PATCH 08/10] Highlight Future and Stream --- text/0000-async_await.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 6a9d0ce3590..2576d0da036 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -365,9 +365,9 @@ return types other than `Task`. We do not have a compelling use case for this: methods in object-safe traits. This is a special case of supporting `impl Trait` in object-safe traits (probably by boxing the return type in the object case), a feature we want separately from `async fn`. -3. It has been proposed that we support `async fn` which return streams. +3. It has been proposed that we support `async fn` which return `Stream`s. However, this mean that the semantics of the internal function would differ - significantly between those which return futures and streams. As discussed + significantly between those which return `Future`s and `Stream`s. As discussed in the unresolved questions section, a solution based on generators and `async` generators seems more promising. @@ -429,7 +429,7 @@ returns `()` would implement `Iterator`. The problem with this approach is that does not ergonomically handle `Stream`s, which need to yield `Poll>`. It's unclear how `await` inside of an -`async` fn yielding something other than `()` (which would include streams) +`async` fn yielding something other than `()` (which would include `Stream`s) would work. For this reason, the "matrix" approach in which we have independent syntax for generator functions, `async` functions, and `async` generator functions, seems like a more promising approach. @@ -573,11 +573,11 @@ There are a couple of possible solutions: This is left as an unresolved question to find another solution or decide which of these is least bad. -## `for await` and processing streams +## `for await` and processing `Stream`s -Another extension left out of the RFC for now is the ability to process streams -using a for loop. One could imagine a construct like `for await`, which takes -an `IntoStream` instead of an `IntoIterator`: +Another extension left out of the RFC for now is the ability to process +`Stream`s using a for loop. One could imagine a construct like `for await`, +which takes an `IntoStream` instead of an `IntoIterator`: ```rust for await value in stream { @@ -592,9 +592,9 @@ small as possible). ## Generators and Streams In the future, we may also want to be able to define `async` functions that -evaluate to streams, rather than evaluating to `Future`s. We propose to handle +evaluate to `Stream`s, rather than evaluating to `Future`s. We propose to handle this use case by way of generators. Generators can evaluate to a kind of -iterator, while `async` generators can evaluate to a kind of stream. +iterator, while `async` generators can evaluate to a kind of `Stream`. For example (using syntax which could change); @@ -607,7 +607,7 @@ fn foo(mut x: i32) yield i32 { } } -// Returns a stream of i32 +// Returns a Stream of i32 async fn foo(io: &AsyncRead) yield i32 { for await line in io.lines() { yield line.unwrap().parse().unwrap(); From d890d6124c0347aa879bdee32c1e2c62df74cc91 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Fri, 4 May 2018 13:42:37 +0200 Subject: [PATCH 09/10] Highlight return keyword --- text/0000-async_await.md | 68 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 2576d0da036..21280692fb3 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -95,13 +95,13 @@ fn main() { This will print `"Hello from main"` before printing `"Hello from print_async"`. An `async fn foo(args..) -> T` is a function of the type -`fn(args..) -> impl Future`. The return type is an anonymous type +`fn(args..) -> impl Future`. The `return` type is an anonymous type generated by the compiler. ### `async ||` closures In addition to functions, `async` can also be applied to closures. Like an -`async` function, an `async` closure has a return type of +`async` function, an `async` closure has a `return` type of `impl Future`, rather than `T`. When you call that closure, it returns a `Future` immediately without evaluating any of the body (just like an `async` function). @@ -185,11 +185,11 @@ Both `async` and `await` become keywords, gated on the 2018 edition. ## Return type of `async` functions, closures, and blocks -The return type of an `async` function is a unique anonymous type which implements +The `return` type of an `async` function is a unique anonymous type which implements `Future` and is generated by the compiler, similar to the type of a closure. You can think of this type as being like an enum, with one variant for every "yield point" of the function - the beginning of it, the `await` expressions, -and every return. Each variant stores the state that is needed to be stored to +and every `return`. Each variant stores the state that is needed to be stored to resume control from that yield point. When the function is called, this anonymous type is returned in its initial @@ -197,13 +197,13 @@ state, which contains all of the arguments to this function. ### Trait bounds -The anonymous return type implements `Future`, with the return type as its +The anonymous `return` type implements `Future`, with the `return` type as its `Output`. Polling it advances the state of the function, returning `Pending` when it hits an `await` point, and `Ready` with the item when it hits a -return point. Any attempt to poll it after it has already returned `Ready` +`return` point. Any attempt to poll it after it has already returned `Ready` once will panic. -The anonymous return type has a negative impl for the `Unpin` trait - that is +The anonymous `return` type has a negative impl for the `Unpin` trait - that is `impl !Unpin`. This is because the `Future` could have internal references which means it needs to never be moved. @@ -225,7 +225,7 @@ fn foo<'a>(arg: &'a str) -> impl Future + 'a { ... } ``` This is different from the default for `impl Trait`, which does not capture the -lifetime. This is a big part of why the return type is `T` instead of `impl +lifetime. This is a big part of why the `return` type is `T` instead of `impl Future`. ### "Initialization" pattern @@ -305,8 +305,8 @@ costly because this exists. However, given our experience with s, we are confident that this is the correct path forward. There are drawbacks to several of the smaller decisions we have made as well. -There is a trade off between using the "inner" return type and the "outer" -return type, for example. We could have a different evaluation model for `async` +There is a trade off between using the "inner" `return` type and the "outer" +`return` type, for example. We could have a different evaluation model for `async` functions in which they are evaluated immediately up to the first `await` point. The decisions we made on each of these questions are justified in the appropriate section of the RFC. @@ -317,24 +317,24 @@ appropriate section of the RFC. This section contains alternative design decisions which this RFC rejects (as opposed to those it merely postpones). -## The return type (`T` instead of `impl Future`) +## The `return` type (`T` instead of `impl Future`) -The return type of an asynchronous function is a sort of complicated question. -There are two different perspectives on the return type of an `async fn`: the -"interior" return type - the type that you return with the `return` keyword, -and the "exterior" return type - the type that the function returns when you +The `return` type of an asynchronous function is a sort of complicated question. +There are two different perspectives on the `return` type of an `async fn`: the +"interior" `return` type - the type that you `return` with the `return` keyword, +and the "exterior" `return` type - the type that the function returns when you call it. -in the function signature. This RFC proposes instead to display the "inner" -Most statically typed languages with `async fn`s display the "outer" return type -return type in the function signature. This has both advantages and +Most statically typed languages with `async fn`s display the "outer" `return` +type in the function signature. This RFC proposes instead to display the "inner" +`return` type in the function signature. This has both advantages and disadvantages. ### The lifetime elision problem As eluded to previously, the returned `Future` captures all input lifetimes. By default, `impl Trait` does not capture any lifetimes. To accurately reflect the -outer return type, it would become necessary to eliminate lifetime elision: +outer `return` type, it would become necessary to eliminate lifetime elision: ```rust async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Future + 'ret { @@ -347,10 +347,10 @@ and much less easy to learn. This issue weighs heavily in the decision to prefer returning the interior type. We could have it return `impl Future` but have lifetime capture work -differently for the return type of `async fn` than other functions; this seems +differently for the `return` type of `async fn` than other functions; this seems worse than showing the interior type. -### Polymorphic return (a non-factor for us) +### Polymorphic `return` (a non-factor for us) According to the C# developers, one of the major factors in returning `Task` (their "outer type") was that they wanted to have `async` functions which could @@ -363,7 +363,7 @@ return types other than `Task`. We do not have a compelling use case for this: have `async` functions always be unboxed and only box them explicitly at the call site. The motivation for the attribute variant was to support `async` methods in object-safe traits. This is a special case of supporting `impl - Trait` in object-safe traits (probably by boxing the return type in the + Trait` in object-safe traits (probably by boxing the `return` type in the object case), a feature we want separately from `async fn`. 3. It has been proposed that we support `async fn` which return `Stream`s. However, this mean that the semantics of the internal function would differ @@ -377,15 +377,15 @@ to return the outer type. ### Learnability / documentation trade off There are arguments from learnability in favor of both the outer and inner -return type. One of the most compelling arguments in favor of the outer return -type is documentation: when you read automatically generated API docs, you will -definitely see what you get as the caller. In contrast, it can be easier to -understand how to write an `async` function using the inner return type, because -of the correspondence between the return type and the type of the expressions -you `return`. - -Rustdoc can handle `async` functions using the inner return type in a couple of -ways to make them easier to understand. At minimum we should make sure to +`return` type. One of the most compelling arguments in favor of the outer +`return` type is documentation: when you read automatically generated API docs, +you will definitely see what you get as the caller. In contrast, it can be +easier to understand how to write an `async` function using the inner `return` +type, because of the correspondence between the `return` type and the type of the +expressions you `return`. + +Rustdoc can handle `async` functions using the inner `return` type in a couple +of ways to make them easier to understand. At minimum we should make sure to include the `async` annotation in the documentation, so that users who understand `async` notation know that the function will return a `Future`. We can also perform other transformations, possibly optionally, to display the @@ -423,7 +423,7 @@ creating generators. In this design, we would write a generator like this: async fn foo(arg: Arg) -> Return yield Yield ``` -Both return and yield would be optional, default to `()`. An `async fn` that +Both `return` and `yield` would be optional, default to `()`. An `async fn` that yields `()` would implement `Future`, using a blanket impl. An `async fn` that returns `()` would implement `Iterator`. @@ -530,9 +530,9 @@ green-threading). One way in which our handling of `async`/`await` differs from most other statically typed languages (such as C#) is that we have chosen to show the -"inner" return type, rather than the outer return type. As discussed in the +"inner" `return` type, rather than the outer `return` type. As discussed in the alternatives section, Rust's specific context (lifetime elision, the lack of a -need for return type polymorphism here) make this deviation well-motivated. +need for `return` type polymorphism here) make this deviation well-motivated. # Unresolved questions [unresolved]: #unresolved-questions From de25e2116dcf382e83077b5593820d05c95f12bd Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Fri, 4 May 2018 13:51:11 +0200 Subject: [PATCH 10/10] Highlight enum --- text/0000-async_await.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 21280692fb3..45790e23994 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -187,7 +187,7 @@ Both `async` and `await` become keywords, gated on the 2018 edition. The `return` type of an `async` function is a unique anonymous type which implements `Future` and is generated by the compiler, similar to the type of a closure. -You can think of this type as being like an enum, with one variant for every +You can think of this type as being like an `enum`, with one variant for every "yield point" of the function - the beginning of it, the `await` expressions, and every `return`. Each variant stores the state that is needed to be stored to resume control from that yield point.