Skip to content

Commit

Permalink
Rename Async trait to Task
Browse files Browse the repository at this point in the history
  • Loading branch information
MajorBreakfast committed Apr 25, 2018
1 parent f3e1438 commit 144f448
Showing 1 changed file with 71 additions and 70 deletions.
141 changes: 71 additions & 70 deletions text/0000-async_await.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
[summary]: #summary

Add `async` & `await` syntaxes to make it more ergonomic to construct types that
implement `Async`.
implement `Task`.

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
Note: `Task` was previously called `Future`. However, due to to the many
subtle differeneces, it was decided to standardize it under a different,
slightly shorter name.

Expand Down Expand Up @@ -43,16 +43,17 @@ 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.

`async`/`await` & `Async` can be a powerful abstraction for asynchronicity and
Fortunately, the future abstraction (in some languages also called "promises",
this RFC calls them "tasks") 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 `Task`, rather than evaluating immediately, when it is
called. Inside the function, other `Task` types can be awaited using an `await`
expression, which causes them to yield control while the `Task` is being polled.
From a user's perspective, code that uses `async`/`await` feels very similar
synchronous code.

`async`/`await` & `Task` 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
Expand All @@ -75,12 +76,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 `Async`. As that `Async` is polled, the
function is evaluated up to the next `await` or return point inside of it (see
anonymous type which implements `Task`. As that `Task` 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 `Task` returned by the
function. For example:

```rust
Expand All @@ -89,23 +90,23 @@ async fn print_async() {
}

fn main() {
let my_async = print_async();
let task = print_async();
println!("Hello from main");
futures::block_on(my_async);
futures::block_on(task);
}
```

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<Output = T>`. The return type is an anonymous type
`fn(args..) -> impl Task<Output = T>`. 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<Output = T>`, rather
than `T`. When you call that closure, it returns an `Async` immediately without
function, an async closure has a return type of `impl Task<Output = T>`, rather
than `T`. When you call that closure, it returns a `Task` immediately without
evaluating any of the body (just like an async function).

```rust
Expand All @@ -114,9 +115,9 @@ fn main() {
println("Hello from async closure.");
};
println!("Hello from main");
let my_async = closure();
let task = closure();
println!("Hello from main again");
futures::block_on(my_async);
futures::block_on(task);
}
```

Expand All @@ -128,10 +129,10 @@ variables they close over.

## `async` blocks

You can create an `Async` directly as an expression using an async block:
You can create a `Task` directly as an expression using an async block:

```rust
let my_async = async {
let task = async {
println!("Hello from an async block");
};
```
Expand Down Expand Up @@ -159,16 +160,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
"pause" the computation of the `Task`, yielding control back to the caller.
`await!` takes any expression which implements `IntoTask`, and evaluates to a
value of the item type that `future` has.

```rust
// my_async: impl Async<Output = usize>
let n = await!(my_async);
// task: impl Task<Output = usize>
let n = await!(task);
```

The expansion of await repeatedly calls `poll` on the `Async` it receives,
The expansion of await repeatedly calls `poll` on the `Task` it receives,
yielding control of the function when it returns `Poll::Pending` and
eventually evaluating to the item value when it returns `Poll::Ready`.

Expand All @@ -188,7 +189,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.
`Task` 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
Expand All @@ -199,19 +200,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 `Task`, 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 `Task` could have internal references which
means it needs to never be moved.

## Lifetime capture in the anonymous `Async`
## Lifetime capture in the anonymous `Task`

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 `Task` 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:
Expand All @@ -223,27 +224,27 @@ async fn foo(arg: &str) -> usize { ... }
It has an equivalent type signature to this:

```rust
fn foo<'a>(arg: &'a str) -> impl Async<Output = usize> + 'a { ... }
fn foo<'a>(arg: &'a str) -> impl Task<Output = usize> + '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<Output = T>`.
Task<Output = T>`.

### "Initialization" pattern

One pattern that sometimes occurs is that an `Async` has an "initialization"
One pattern that sometimes occurs is that a `Task` 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 Task` 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<Output = usize> + 'a {
// only arg1's lifetime is captured in the returned Task
fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Task<Output = usize> + 'a {
// do some initialization using arg2

// closure which is evaluated immediately
Expand All @@ -258,10 +259,10 @@ fn foo<'a>(arg1: &'a str, arg2: &str) -> impl Async<Output = usize> + '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 task = IntoTask::into_task($expression);
let mut pin = unsafe { Pin::new_unchecked(&mut task) };
loop {
match Async::poll(Pin::borrow(&mut pin), &mut ctx) {
match Task::poll(Pin::borrow(&mut pin), &mut ctx) {
Poll::Ready(item) => break item,
Poll::Pending => yield,
}
Expand Down Expand Up @@ -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 `Task` 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
`Task` & `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
Expand All @@ -319,7 +320,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<Output = T>`)
## The return type (`T` instead of `impl Task<Output = T>`)

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
Expand All @@ -334,12 +335,12 @@ disadvantages.

### The lifetime elision problem

As eluded to previously, the returned `Async` captures all input lifetimes. By
As eluded to previously, the returned `Task` 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<Output = i32> + 'ret {
async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Task<Output = i32> + 'ret {
*x + *y
}
```
Expand All @@ -348,7 +349,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 Task` but have lifetime capture work
differently for the return type of `async fn` than other functions; this seems
worse than showing the interior type.

Expand Down Expand Up @@ -389,7 +390,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 `Task`.
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.
Expand Down Expand Up @@ -426,7 +427,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 `Task`, 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,
Expand All @@ -446,29 +447,29 @@ 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 `Task` being awaited,
conditional on whether or not the await is the first await in the body of the
`Async`.
`Task`.

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 `Task`s and those from other languages
is that Rust's `Task`s do not do anything unless polled. The whole system is
built around this: for example, cancellation is dropping the `Task` 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 `Task` 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 `Task` 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 `Task` 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 `Task`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.
Expand Down Expand Up @@ -503,7 +504,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 `Task`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
Expand All @@ -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).
Expand All @@ -550,27 +551,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 `Task` 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 my_async?
await task?
```

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 { task }?` - 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 task)?` - 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? task` - 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.
Expand All @@ -594,7 +595,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 `Task`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.

Expand Down Expand Up @@ -640,7 +641,7 @@ let reader: AsyncRead = ...;
async {
let foo = await!(reader.read_to_end())?;
Ok(foo.parse().unwrap_or(0))
}: impl Async<Output = io::Result<u32>>
}: impl Task<Output = io::Result<u32>>
```

Also, it was discussed to allow the use of `break` to return early from
Expand Down

0 comments on commit 144f448

Please sign in to comment.