Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async-await initial reference material #635

Merged
merged 1 commit into from
Aug 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
- [If and if let expressions](expressions/if-expr.md)
- [Match expressions](expressions/match-expr.md)
- [Return expressions](expressions/return-expr.md)
- [Await expressions](expressions/await-expr.md)

- [Patterns](patterns.md)

Expand Down
4 changes: 4 additions & 0 deletions src/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
>       | [_OperatorExpression_]\
>       | [_GroupedExpression_]\
>       | [_ArrayExpression_]\
>       | [_AwaitExpression_]\
>       | [_IndexExpression_]\
>       | [_TupleExpression_]\
>       | [_TupleIndexingExpression_]\
Expand All @@ -33,6 +34,7 @@
> &nbsp;&nbsp; [_OuterAttribute_]<sup>\*</sup>[†](#expression-attributes)\
> &nbsp;&nbsp; (\
> &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; [_BlockExpression_]\
> &nbsp;&nbsp; &nbsp;&nbsp; | [_AsyncBlockExpression_]\
> &nbsp;&nbsp; &nbsp;&nbsp; | [_UnsafeBlockExpression_]\
> &nbsp;&nbsp; &nbsp;&nbsp; | [_LoopExpression_]\
> &nbsp;&nbsp; &nbsp;&nbsp; | [_IfExpression_]\
Expand Down Expand Up @@ -324,6 +326,8 @@ They are never allowed before:

[_ArithmeticOrLogicalExpression_]: expressions/operator-expr.md#arithmetic-and-logical-binary-operators
[_ArrayExpression_]: expressions/array-expr.md
[_AsyncBlockExpression_]: expressions/block-expr.md#async-blocks
[_AwaitExpression_]: expressions/await-expr.md
[_AssignmentExpression_]: expressions/operator-expr.md#assignment-expressions
[_BlockExpression_]: expressions/block-expr.md
[_BreakExpression_]: expressions/loop-expr.md#break-expressions
Expand Down
68 changes: 68 additions & 0 deletions src/expressions/await-expr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Await expressions

> **<sup>Syntax</sup>**\
> _AwaitExpression_ :\
> &nbsp;&nbsp; [_Expression_] `.` `await`

Await expressions are legal only within an [async context], like an
[`async fn`] or an [`async` block]. They operate on a [future]. Their effect
is to suspend the current computation until the given future is ready
to produce a value.

More specifically, an `<expr>.await` expression has the following effect.

1. Evaluate `<expr>` to a [future] `tmp`;
2. Pin `tmp` using [`Pin::new_unchecked`];
3. This pinned future is then polled by calling the [`Future::poll`] method and
passing it the current [task context](#task-context);
3. If the call to `poll` returns [`Poll::Pending`], then the future
returns `Poll::Pending`, suspending its state so that, when the
surrounding async context is re-polled, execution returns to step
2;
4. Otherwise the call to `poll` must have returned [`Poll::Ready`], in which case the
value contained in the [`Poll::Ready`] variant is used as the result
of the `await` expression itself.

[`async fn`]: ../items/functions.md#async-functions
[`async` block]: block-expr.md#async-blocks
[future]: ../../std/future/trait.Future.html
[_Expression_]: ../expressions.md
[`Future::poll`]: ../../std/future/trait.Future.html#tymethod.poll
[`Context`]: ../../std/task/struct.Context.html
[`Pin::new_unchecked`]: ../../std/pin/struct.Pin.html#method.new_unchecked
[`Poll::Pending`]: ../../std/task/enum.Poll.html#variant.Pending
[`Poll::Ready`]: ../../std/task/enum.Poll.html#variant.Ready

nikomatsakis marked this conversation as resolved.
Show resolved Hide resolved
> **Edition differences**: Await expressions are only available beginning with
> Rust 2018.

## Task context

The task context refers to the [`Context`] which was supplied to the
current [async context] when the async context itself was
polled. Because `await` expressions are only legal in an async
context, there must be some task context available.

[`Context`]: ../../std/task/struct.Context.html
[async context]: ../expressions/block-expr.md#async-context

## Approximate desugaring

Effectively, an `<expr>.await` expression is roughly
equivalent to the following (this desugaring is not normative):

```rust,ignore
let future = /* <expr> */;
loop {
let mut pin = unsafe { Pin::new_unchecked(&mut future) };
match Pin::future::poll(Pin::borrow(&mut pin), &mut current_context) {
Poll::Ready(r) => break r,
Poll::Pending => yield Poll::Pending,
}
}
```

where the `yield` pseudo-code returns `Poll::Pending` and, when
re-invoked, resumes execution from that point. The variable
`current_context` refers to the context taken from the async
environment.
68 changes: 68 additions & 0 deletions src/expressions/block-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,74 @@ fn move_by_block_expression() {
}
```

## `async` blocks

> **<sup>Syntax</sup>**\
> _AsyncBlockExpression_ :\
> &nbsp;&nbsp; `async` `move`<sup>?</sup> _BlockExpression_

An *async block* is a variant of a block expression which evaluates to
a *future*. The final expression of the block, if present, determines
the result value of the future.

Executing an async block is similar to executing a closure expression:
its immediate effect is to produce and return an anonymous type.
Whereas closures return a type that implements one or more of the
[`std::ops::Fn`] traits, however, the type returned for an async block
implements the [`std::future::Future`] trait. The actual data format for
this type is unspecified.

> **Note:** The future type that rustc generates is roughly equivalent
> to an enum with one variant per `await` point, where each variant
> stores the data needed to resume from its corresponding point.

> **Edition differences**: Async blocks are only available beginning with Rust 2018.

[`std::ops::Fn`]: ../../std/ops/trait.Fn.html
[`std::future::Future`]: ../../std/future/trait.Future.html

### Capture modes

Async blocks capture variables from their environment using the same
[capture modes] as closures. Like closures, when written `async {
.. }` the capture mode for each variable will be inferred from the
content of the block. `async move { .. }` blocks however will move all
referenced variables into the resulting future.

[capture modes]: ../types/closure.md#capture-modes
[shared references]: ../types/pointer.md#shared-references-
[mutable reference]: ../types/pointer.md#mutables-references-

### Async context

Because async blocks construct a future, they define an **async
context** which can in turn contain [`await` expressions]. Async
contexts are established by async blocks as well as the bodies of
async functions, whose semantics are defined in terms of async blocks.

[`await` expressions]: await-expr.md

### Control-flow operators

Async blocks act like a function boundary, much like
closures. Therefore, the `?` operator and `return` expressions both
affect the output of the future, not the enclosing function or other
context. That is, `return <expr>` from within a closure will return
the result of `<expr>` as the output of the future. Similarly, if
`<expr>?` propagates an error, that error is propagated as the result
of the future.

Finally, the `break` and `continue` keywords cannot be used to branch
out from an async block. Therefore the following is illegal:

```rust,edition2018,compile_fail
loop {
async move {
break; // This would break out of the loop.
}
}
```

## `unsafe` blocks

> **<sup>Syntax</sup>**\
Expand Down
103 changes: 102 additions & 1 deletion src/items/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
> &nbsp;&nbsp; &nbsp;&nbsp; [_BlockExpression_]
>
> _FunctionQualifiers_ :\
> &nbsp;&nbsp; `const`<sup>?</sup> `unsafe`<sup>?</sup> (`extern` _Abi_<sup>?</sup>)<sup>?</sup>
> &nbsp;&nbsp; _AsyncConstQualifiers_<sup>?</sup> `unsafe`<sup>?</sup> (`extern` _Abi_<sup>?</sup>)<sup>?</sup>
>
> _AsyncConstQualifiers_ :\
> &nbsp;&nbsp; `async` | `const`
>
> _Abi_ :\
> &nbsp;&nbsp; [STRING_LITERAL] | [RAW_STRING_LITERAL]
Expand Down Expand Up @@ -189,6 +192,104 @@ Exhaustive list of permitted structures in const functions:
the following unsafe operations:
* calls to const unsafe functions

## Async functions

Functions may be qualified as async, and this can also be combined with the
`unsafe` qualifier:

```rust,edition2018
async fn regular_example() { }
async unsafe fn unsafe_example() { }
```

Async functions do no work when called: instead, they
capture their arguments into a future. When polled, that future will
execute the function's body.

An async function is roughly equivalent to a function
that returns [`impl Future`] and with an [`async move` block][async-blocks] as
its body:

```rust,edition2018
ehuss marked this conversation as resolved.
Show resolved Hide resolved
// Source
async fn example(x: &str) -> usize {
x.len()
}
```

is roughly equivalent to:

```rust,edition2018
# use std::future::Future;
// Desugared
fn example<'a>(x: &'a str) -> impl Future<Output = usize> + 'a {
async move { x.len() }
}
```

The actual desugaring is more complex:

- The return type in the desugaring is assumed to capture all lifetime
parameters from the `async fn` declaration. This can be seen in the
desugared example above, which explicitly outlives, and hence
captures, `'a`.
- The [`async move` block][async-blocks] in the body captures all function
parameters, including those that are unused or bound to a `_`
pattern. This ensures that function parameters are dropped in the
same order as they would be if the function were not async, except
that the drop occurs when the returned future has been fully
awaited.

For more information on the effect of async, see [`async` blocks][async-blocks].

[async-blocks]: ../expressions/block-expr.md#async-blocks
[`impl Future`]: ../types/impl-trait.md

> **Edition differences**: Async functions are only available beginning with
> Rust 2018.

### Combining `async` and `unsafe`

It is legal to declare a function that is both async and unsafe. The
resulting function is unsafe to call and (like any async function)
returns a future. This future is just an ordinary future and thus an
`unsafe` context is not required to "await" it:

```rust,edition2018
// Returns a future that, when awaited, dereferences `x`.
//
// Soundness condition: `x` must be safe to dereference until
// the resulting future is complete.
async unsafe fn unsafe_example(x: *const i32) -> i32 {
*x
}

async fn safe_example() {
// An `unsafe` block is required to invoke the function initially:
let p = 22;
let future = unsafe { unsafe_example(&p) };

// But no `unsafe` block required here. This will
// read the value of `p`:
let q = future.await;
}
```

Note that this behavior is a consequence of the desugaring to a
function that returns an `impl Future` -- in this case, the function
we desugar to is an `unsafe` function, but the return value remains
the same.

Unsafe is used on an async function in precisely the same way that it
is used on other functions: it indicates that the function imposes
some additional obligations on its caller to ensure soundness. As in any
other unsafe function, these conditions may extend beyond the initial
call itself -- in the snippet above, for example, the `unsafe_example`
function took a pointer `x` as argument, and then (when awaited)
dereferenced that pointer. This implies that `x` would have to be
valid until the future is finished executing, and it is the callers
responsibility to ensure that.

## Attributes on functions

[Outer attributes][attributes] are allowed on functions. [Inner
Expand Down