From f63ddca7ce5cd8725ec137459ba2a930474a34e7 Mon Sep 17 00:00:00 2001 From: boats Date: Thu, 5 Apr 2018 05:34:04 -0700 Subject: [PATCH 01/15] Async/await. --- text/0000-async_await.md | 571 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 571 insertions(+) create mode 100644 text/0000-async_await.md diff --git a/text/0000-async_await.md b/text/0000-async_await.md new file mode 100644 index 00000000000..5e6b11a01f1 --- /dev/null +++ b/text/0000-async_await.md @@ -0,0 +1,571 @@ +- Feature Name: async_await +- Start Date: 2018-03-30 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Add async & await syntaxes to make it more ergonomic to write code manipulating +futures. + +This has a companion RFC to add a small futures API to libstd and libcore. + +# Motivation +[motivation]: #motivation + +High performance network services frequently use asynchronous IO, rather than +blocking IO, becuase it can be easier to get optimal performance when handling +many concurrent connections. Rust has seen some adoption in the network +services space, and we wish to continue to enable those users - and to enable +adoption by other users - by making it more ergonomic to write asynchronous +network services in Rust. + +The development of asynchronous IO in Rust has gone through multiple phases. +Prior to 1.0, we experimented with having a greenthreading runtime built into +the language. However, this proved too opinionated - because it impacted every +program written in Rust - and it was removed shortly before 1.0. After 1.0, +asynchronous IO initially focused around the mio library, which provided an +cross-platform abstraction over the async IO primitives of Linux, Mac OS, and +Windows. In mid-2016, the introduction of the futures crate had a major impact +by providing a convenient, shared abstraction for asynchronous operations. The +tokio library provided a mio-based event loop that could execute code +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 ergonomic - 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 +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. + +Async/await & futures 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. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +## Async functions + +Functions can be annotated with the `async` keyword, making them "async +functions": + +```rust +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 +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. For example: + +```rust +async fn print_async() { + println!("Hello from print_async") +} + +fn main() { + let future = print_async(); + println!("Hello from main"); + 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 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 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() { + let closure = async || { + println("Hello from async closure."); + }; + println!("Hello from main"); + let future = closure(); + println!("Hello from main again"); + futures::block_on(future); +} +``` + +This will print both "Hello from main" statements before printing "Hello from +async closure." + +## 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. + +```rust +// future: impl Future +let n = await!(future); +``` + +The expansion of await repeatedly calls `poll` on the future it receives, +yielding control of the function when it returns `Async::Pending` and +eventually evaluating to the item value when it returns `Async::Ready`. + +`await!` can only be used inside of an async function or an async closure. +Using outside of that context is an error. + +(`await!` is a compiler built-in to leave space for deciding its exact syntax +later. See more information in the unresolved questions section.) + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## Return type of `async` functions (and closures) + +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. + +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 +`Item`. 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 +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 +in its initial state (and possibly later states). That is, given a function +like this: + +```rust +async fn foo(arg: &str) -> usize { ... } +``` + +It has an equivalent type signature to this: + +```rust +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 +Future`. + +### "Initialization" pattern + +One pattern that sometimes occurs is that a future has an "initialization" step +which could ideally happen before the future starts being polled. 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 with a single +`async fn`. + +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 future +fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { + // do some initialization using arg2 + + // closure which is evaluated immediately + (async move || { + // asynchronous portion of the function + })() +} +``` + +The futures crate will provide an `async_block!` macro to make this cleaner: + +```rust +// 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 + async_block! { + // asynchronous portion of the function + } +} +``` + +(Eventually, `async_block!` may be upgraded to a first class syntax, like +`async { ... }`, but this is left as an unresolved question.) + +## The expansion of await + +The `await!` builtin expands roughly to this: + +```rust +let mut future = IntoFuture::into_future($expression); +let mut pin = unsafe { Pin::new_unchecked(&mut future) }; +loop { + match Future::poll(Pin::borrow(&mut pin), &mut ctx) { + Async::Ready(item) => break item, + Async::Pending => yield, + } +} +``` + +This is not a literal expansion, because the `yield` concept cannot be +expressed in the surface syntax within `async` functions. This is why `await!` +is a compiler builtin instead of an actual macro. + +# Drawbacks +[drawbacks]: #drawbacks + +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 +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 +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 +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 +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. +The decisions we made on each of these questions are justified in the +appropriate section of the RFC. + +# Rationale and alternatives +[alternatives]: #alternatives + +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 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. + +Most statically typed languages with async fns 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: + +```rust +async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Future + 'ret { + *x + *y +} +``` + +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 +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) + +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 +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 + 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. + 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. + +For these reasons, we don't think there's a strong argument from polymorphism +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 +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. + +## 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 `()`. + +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. + +It seems unlikely that using macros which expand to generators will result in a +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. + +## `async` based on generators alone + +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 +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 +returns `()` would implement `Iterator`. + +The problem with this approach is that does not ergonomically handle `Stream`s, +which need to yield `Async`. 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. + +## "Hot async functions" + +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. + +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 +future. + +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. + +## 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, +greenthreading, and stackfull 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 nearterm. 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. + +# 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#, +JavaScript, and Python. + +There are three paradigms for asynchronous programming which are dominant +today: + +- Async and await notation. +- An implicit concurrent runtime, often called "greenthreading," 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 +favorably with ownership and borrowing (unlike systems based on monads) and it +enables us to have an entirely library-based asynchronicity model (unlike +greenthreading). + +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 + +This section contains design extensions which have been postponed & not +included in this initial RFC. + +## Final syntax for the `await` expression + +Though this RFC proposes that `await` be a built-in macro, we'd prefer that +some day it be a normal control flow construct. The unresolved question about +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 +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? +``` + +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. +2. Define the precedence as the obvious, if inconvient precedence, requiring + users to write `(await future)?` - this seems very surprising for users. +3. Define the precedence as the inconvient 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. + +This is left as an unresolved question to find another solution or decide which +of these is least bad. + +## `async for` and processing streams + +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 `async for`, which takes +an `IntoStream` instead of an `IntoIterator`: + +```rust +async for value in stream { + println!("{}", value); +} +``` + +This is left out of the initial RFC to avoid having to stabilize a definition +of `Stream` in the standard library (to keep the companion RFC to this one as +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 +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. + +For example (using syntax which could change); + +```rust +// Returns an iterator of i32 +fn foo(mut x: i32) yield i32 { + while x > 0 { + yield x; + x -= 2; + } +} + +// Returns a stream of i32 +async fn foo(io: &AsyncRead) yield i32 { + async for line in io.lines() { + yield line.unwrap().parse().unwrap(); + } +} +``` + +## Async functions which implement `Unpin` + +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, typeck 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. + +## Async blocks as a first class syntax + +In the future, we could support "async blocks" - a block which evaluates to a +future rather than evaluating its content immediately: + +```rust +let future = async { + println!("Hello from async block"); +}; +println!("Hello from main"); +block_on(future); +``` + +This is equivalent to the more verbose and confusing `async move || { }()`; for +the time being a macro in the futures crate called `async_block!` will expand +to the immediately-called closure form, and we will leave the question of async +blocks to the future. From 8c3d18950a598b5c1b6de64767b0cbceacdc77fb Mon Sep 17 00:00:00 2001 From: Vincent Esche Date: Mon, 9 Apr 2018 09:06:38 +0200 Subject: [PATCH 02/15] Fixed typos --- text/0000-async_await.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 5e6b11a01f1..8e952a49491 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -15,14 +15,14 @@ This has a companion RFC to add a small futures API to libstd and libcore. [motivation]: #motivation High performance network services frequently use asynchronous IO, rather than -blocking IO, becuase it can be easier to get optimal performance when handling +blocking IO, because it can be easier to get optimal performance when handling many concurrent connections. Rust has seen some adoption in the network services space, and we wish to continue to enable those users - and to enable adoption by other users - by making it more ergonomic to write asynchronous network services in Rust. The development of asynchronous IO in Rust has gone through multiple phases. -Prior to 1.0, we experimented with having a greenthreading runtime built into +Prior to 1.0, we experimented with having a green-threading runtime built into the language. However, this proved too opinionated - because it impacted every program written in Rust - and it was removed shortly before 1.0. After 1.0, asynchronous IO initially focused around the mio library, which provided an @@ -425,11 +425,11 @@ initialization step. 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, -greenthreading, and stackfull coroutines. +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 nearterm. Given our goals for 2018 - which emphasize +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. @@ -445,7 +445,7 @@ There are three paradigms for asynchronous programming which are dominant today: - Async and await notation. -- An implicit concurrent runtime, often called "greenthreading," such as +- 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). @@ -453,7 +453,7 @@ today: 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 -greenthreading). +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 @@ -490,9 +490,9 @@ 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. -2. Define the precedence as the obvious, if inconvient precedence, requiring +2. Define the precedence as the obvious, if inconvenient precedence, requiring users to write `(await future)?` - this seems very surprising for users. -3. Define the precedence as the inconvient precedence - this seems equally +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. @@ -548,7 +548,7 @@ 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, typeck an async function to confirm that it +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. From 7cfd7558919e88407edc3b206e37d64bce0ed2ae Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 20 Apr 2018 15:19:09 -0700 Subject: [PATCH 03/15] Typo fix --- text/0000-async_await.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 8e952a49491..e9a2594419b 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 ergonomic - requiring either Arcs or join +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 callbacks. @@ -240,7 +240,7 @@ loop { match Future::poll(Pin::borrow(&mut pin), &mut ctx) { Async::Ready(item) => break item, Async::Pending => yield, - } + } } ``` From 411ad1f9febc7f111d57c1d8f93edc2d2bb29616 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 20 Apr 2018 15:25:28 -0700 Subject: [PATCH 04/15] Add async blocks --- text/0000-async_await.md | 69 ++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 38 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index e9a2594419b..b546ce2f6db 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -119,6 +119,34 @@ fn main() { This will print both "Hello from main" statements before printing "Hello from async closure." +## `async` blocks + +You can create a future directly as an expression using an `async` block: + +```rust +let my_future = async { + println!("Hello from an async block"); +}; +``` + +This form is equivalent to an immediately-invoked `async` closure. +That is: + +```rust +async { /* body */ } + +// is equivalent to + +(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). + +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 @@ -135,7 +163,7 @@ The expansion of await repeatedly calls `poll` on the future it receives, yielding control of the function when it returns `Async::Pending` and eventually evaluating to the item value when it returns `Async::Ready`. -`await!` can only be used inside of an async function or an async closure. +`await!` can only be used inside of an async function, closure, or block. Using outside of that context is an error. (`await!` is a compiler built-in to leave space for deciding its exact syntax @@ -144,7 +172,7 @@ later. See more information in the unresolved questions section.) # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## Return type of `async` functions (and closures) +## 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 @@ -206,29 +234,12 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { // do some initialization using arg2 // closure which is evaluated immediately - (async move || { - // asynchronous portion of the function - })() -} -``` - -The futures crate will provide an `async_block!` macro to make this cleaner: - -```rust -// 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 - async_block! { + async move { // asynchronous portion of the function } } ``` -(Eventually, `async_block!` may be upgraded to a first class syntax, like -`async { ... }`, but this is left as an unresolved question.) - ## The expansion of await The `await!` builtin expands roughly to this: @@ -551,21 +562,3 @@ 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 `Unpin`. The annotation to enable this is left unspecified for the time being. - -## Async blocks as a first class syntax - -In the future, we could support "async blocks" - a block which evaluates to a -future rather than evaluating its content immediately: - -```rust -let future = async { - println!("Hello from async block"); -}; -println!("Hello from main"); -block_on(future); -``` - -This is equivalent to the more verbose and confusing `async move || { }()`; for -the time being a macro in the futures crate called `async_block!` will expand -to the immediately-called closure form, and we will leave the question of async -blocks to the future. From 84c7edf0867cc6ee1eb9f5814da6ff71cca02997 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 20 Apr 2018 15:35:53 -0700 Subject: [PATCH 05/15] Rationale for async blocks --- text/0000-async_await.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index b546ce2f6db..e405d9347b4 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -445,6 +445,35 @@ 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 +related, and are roughly inter-expressible: + +```rust +// equivalent +async { ... } +(async || { ... })() + +// equivalent +async |..| { ... } +|..| async { ... } +``` + +We could consider having only one of the two constructs. However: + +- There's a strong reason to have `async ||` for consistency with `async fn`; + 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 + mentioned in the RFC text, and the fact that it provides a more + direct/primitive way of constructing futures. + +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 +before stabilization. + # Prior art [prior-art]: #prior-art From 41ce2017232accd440d79a2cd01dd077ee8e4d2f Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 20 Apr 2018 15:49:25 -0700 Subject: [PATCH 06/15] Add more rationale for lazy async fn eval --- text/0000-async_await.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index e405d9347b4..c2b9aa204fc 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -424,6 +424,15 @@ 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 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, +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 From 15b6c9abe010f7d37c42c16376027cfe13ceead1 Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 20 Apr 2018 16:00:41 -0700 Subject: [PATCH 07/15] Update to match current futures APIs --- text/0000-async_await.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index c2b9aa204fc..6bf98d1631c 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -94,13 +94,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 `impl Future`, rather +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). @@ -155,13 +155,13 @@ A builtin called `await!` is added to the compiler. `await!` can be used to value of the item type that that future has. ```rust -// future: impl Future +// future: impl Future let n = await!(future); ``` The expansion of await repeatedly calls `poll` on the future it receives, -yielding control of the function when it returns `Async::Pending` and -eventually evaluating to the item value when it returns `Async::Ready`. +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. Using outside of that context is an error. @@ -210,12 +210,12 @@ 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 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 -Future`. +Future`. ### "Initialization" pattern @@ -230,7 +230,7 @@ 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 { +fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { // do some initialization using arg2 // closure which is evaluated immediately @@ -249,8 +249,8 @@ let mut future = IntoFuture::into_future($expression); let mut pin = unsafe { Pin::new_unchecked(&mut future) }; loop { match Future::poll(Pin::borrow(&mut pin), &mut ctx) { - Async::Ready(item) => break item, - Async::Pending => yield, + Poll::Ready(item) => break item, + Poll::Pending => yield, } } ``` @@ -293,7 +293,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 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 @@ -313,7 +313,7 @@ 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 Future + 'ret { *x + *y } ``` @@ -404,11 +404,11 @@ 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, -which need to yield `Async`. 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. +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. ## "Hot async functions" From 822c24c90fe218d5b709e42e9c1bbfd315ff0cec Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 20 Apr 2018 16:04:21 -0700 Subject: [PATCH 08/15] Grab keywords --- text/0000-async_await.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 6bf98d1631c..94107d14599 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -172,6 +172,10 @@ later. See more information in the unresolved questions section.) # Reference-level explanation [reference-level-explanation]: #reference-level-explanation +## Keywords + +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 From 685a5886b0df1cfbe59cf35c14c2a298408ae4ac Mon Sep 17 00:00:00 2001 From: Aaron Turon Date: Fri, 20 Apr 2018 16:31:06 -0700 Subject: [PATCH 09/15] Reorder async/move --- 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 94107d14599..26d20afaeb5 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -238,7 +238,7 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { // do some initialization using arg2 // closure which is evaluated immediately - async move { + move async { // asynchronous portion of the function } } From 32b748cfa4dbd859dab23740ad95112bbad8084d Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Tue, 24 Apr 2018 20:46:49 +0200 Subject: [PATCH 10/15] Fix typos --- text/0000-async_await.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 26d20afaeb5..19ca05772ef 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -25,7 +25,7 @@ The development of asynchronous IO in Rust has gone through multiple phases. Prior to 1.0, we experimented with having a green-threading runtime built into the language. However, this proved too opinionated - because it impacted every program written in Rust - and it was removed shortly before 1.0. After 1.0, -asynchronous IO initially focused around the mio library, which provided an +asynchronous IO initially focused around the mio library, which provided a cross-platform abstraction over the async IO primitives of Linux, Mac OS, and Windows. In mid-2016, the introduction of the futures crate had a major impact by providing a convenient, shared abstraction for asynchronous operations. The @@ -164,7 +164,7 @@ 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. -Using outside of that context is an error. +Using it outside of that context is an error. (`await!` is a compiler built-in to leave space for deciding its exact syntax later. See more information in the unresolved questions section.) @@ -193,8 +193,8 @@ state, which contains all of the arguments to this function. The anonymous return type implements `Future`, with the return type as its `Item`. 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. +`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 From 0be451ad7f3e3b0eff49a6961cc0a920017a50fd Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Tue, 24 Apr 2018 21:03:32 +0200 Subject: [PATCH 11/15] Explain use of initialization pattern --- text/0000-async_await.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 19ca05772ef..a8e956cc1b2 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -224,10 +224,10 @@ Future`. ### "Initialization" pattern One pattern that sometimes occurs is that a future has an "initialization" step -which could ideally happen before the future starts being polled. 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 with a single -`async fn`. +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 which is evaluated immediately: From f43ad236c2476846d8933ba7062e9ae16283c8d3 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Tue, 24 Apr 2018 22:18:30 +0200 Subject: [PATCH 12/15] `?`-operator and control-flow constructs in async blocks --- text/0000-async_await.md | 42 +++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index a8e956cc1b2..ad0927b03e0 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -129,7 +129,7 @@ let my_future = async { }; ``` -This form is equivalent to an immediately-invoked `async` closure. +This form is almost equivalent to an immediately-invoked `async` closure. That is: ```rust @@ -142,7 +142,9 @@ 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). +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 ownership of the variables they close over. @@ -464,11 +466,11 @@ As noted in the main text, `async` blocks and `async` closures are closely related, and are roughly inter-expressible: ```rust -// equivalent +// almost equivalent async { ... } (async || { ... })() -// equivalent +// almost equivalent async |..| { ... } |..| async { ... } ``` @@ -479,7 +481,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 futures. @@ -604,3 +606,33 @@ 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 `Unpin`. The annotation to enable this is left unspecified for the time being. + +## `?`-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. + +It was discussed that async blocks should act as a boundary for the +`?`-operator. This would make them suitable for fallible IO: + +```rust +let reader: AsyncRead = ...; +async { + let foo = await!(reader.read_to_end())?; + Ok(foo.parse().unwrap_or(0)) +}: impl Future> +``` + +Also, it was discussed to allow the use of `break` to return early from +an async block: + +```rust +async { + if true { break "foo" } +} +``` + +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. From bcd934a59fc6c2c4f3b2e860c2a56aecbcf2b117 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Tue, 24 Apr 2018 22:33:02 +0200 Subject: [PATCH 13/15] Ordering of async move keywords --- text/0000-async_await.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index ad0927b03e0..27c5a6c86e1 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -119,6 +119,9 @@ fn main() { 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 +variables they close over. + ## `async` blocks You can create a future directly as an expression using an `async` block: @@ -240,7 +243,7 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future + 'a { // do some initialization using arg2 // closure which is evaluated immediately - move async { + async move { // asynchronous portion of the function } } @@ -265,6 +268,19 @@ This is not a literal expansion, because the `yield` concept cannot be expressed in the surface syntax within `async` functions. This is why `await!` 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 +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. + +```rust +async move { + // body +} +``` + # Drawbacks [drawbacks]: #drawbacks From 15c2de48bb5efc4c49f5d32ac8ebd9a2c4fa13b7 Mon Sep 17 00:00:00 2001 From: Josef Reinhard Brandl Date: Tue, 24 Apr 2018 22:34:28 +0200 Subject: [PATCH 14/15] Rename `async for` to `for await` --- text/0000-async_await.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-async_await.md b/text/0000-async_await.md index 27c5a6c86e1..03617302047 100644 --- a/text/0000-async_await.md +++ b/text/0000-async_await.md @@ -571,14 +571,14 @@ 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. -## `async for` and processing streams +## `for await` and processing streams 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 `async for`, which takes +using a for loop. One could imagine a construct like `for await`, which takes an `IntoStream` instead of an `IntoIterator`: ```rust -async for value in stream { +for await value in stream { println!("{}", value); } ``` From 4059d7277cf30d75dfd55bddbe930ca0e6db90fc Mon Sep 17 00:00:00 2001 From: boats Date: Tue, 8 May 2018 14:34:37 -0700 Subject: [PATCH 15/15] RFC 2394 is async/await syntax. --- text/{0000-async_await.md => 2394-async_await.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename text/{0000-async_await.md => 2394-async_await.md} (99%) diff --git a/text/0000-async_await.md b/text/2394-async_await.md similarity index 99% rename from text/0000-async_await.md rename to text/2394-async_await.md index 03617302047..a25b9f6ee10 100644 --- a/text/0000-async_await.md +++ b/text/2394-async_await.md @@ -1,7 +1,7 @@ - Feature Name: async_await - Start Date: 2018-03-30 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: https://github.com/rust-lang/rfcs/pull/2394 +- Rust Issue: https://github.com/rust-lang/rust/issues/50547 # Summary [summary]: #summary