Skip to content

Commit

Permalink
Consistently highlight async and await keywords
Browse files Browse the repository at this point in the history
  • Loading branch information
MajorBreakfast committed May 4, 2018
1 parent ea55d5d commit eb60598
Showing 1 changed file with 65 additions and 64 deletions.
129 changes: 65 additions & 64 deletions text/0000-async_await.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -59,7 +59,7 @@ 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":
Expand All @@ -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:

Expand All @@ -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<Output = T>`, 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<Output = T>`, 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() {
Expand All @@ -118,22 +119,22 @@ 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 {
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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -187,7 +188,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
"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.

Expand All @@ -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.

Expand All @@ -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:

Expand All @@ -231,7 +232,7 @@ Future<Output = T>`.

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`.

Expand All @@ -250,7 +251,7 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Future<Output = usize> + 'a {
}
```

## The expansion of await
## The expansion of `await`

The `await!` builtin expands roughly to this:

Expand All @@ -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.
Expand Down Expand Up @@ -305,8 +306,8 @@ 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.

Expand Down Expand Up @@ -352,23 +353,23 @@ 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<T>`
(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`.
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.
`async` generators seems more promising.

For these reasons, we don't think there's a strong argument from polymorphism
to return the outer type.
Expand All @@ -389,21 +390,21 @@ 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,
Expand All @@ -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
Expand All @@ -430,40 +431,40 @@ The problem with this approach is that does not ergonomically handle `Stream`s,
which need to yield `Poll<Option<T>>`. 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
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
Expand All @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);

Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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.

0 comments on commit eb60598

Please sign in to comment.