Skip to content

Commit

Permalink
Highlight return keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
MajorBreakfast committed May 4, 2018
1 parent 26f6bfe commit d84fb67
Showing 1 changed file with 33 additions and 34 deletions.
67 changes: 33 additions & 34 deletions text/0000-async_await.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,13 @@ fn main() {
This will print `"Hello from main"` before printing `"Hello from print_async"`.

An `async fn foo(args..) -> T` is a function of the type
`fn(args..) -> impl Future<Output = T>`. The return type is an anonymous type
`fn(args..) -> impl Future<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
`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).
Expand Down Expand Up @@ -185,25 +185,25 @@ Both `async` and `await` become keywords, gated on the 2018 edition.

## Return type of `async` functions, closures, and blocks

The return type of an `async` function is a unique anonymous type which implements
The `return` type of an `async` function is a unique anonymous type which implements
`Future` and is generated by the compiler, similar to the type of a closure.
You can think of this type as being like an enum, with one variant for every
"yield point" of the function - the beginning of it, the `await` expressions,
and every return. Each variant stores the state that is needed to be stored to
and every `return`. Each variant stores the state that is needed to be stored to
resume control from that yield point.

When the function is called, this anonymous type is returned in its initial
state, which contains all of the arguments to this function.

### Trait bounds

The anonymous return type implements `Future`, with the return type as its
The anonymous `return` type implements `Future`, with the `return` type as its
`Output`. Polling it advances the state of the function, returning `Pending`
when it hits an `await` point, and `Ready` with the item when it hits a
return point. Any attempt to poll it after it has already returned `Ready`
`return` point. Any attempt to poll it after it has already returned `Ready`
once will panic.

The anonymous return type has a negative impl for the `Unpin` trait - that is
The anonymous `return` type has a negative impl for the `Unpin` trait - that is
`impl !Unpin`. This is because the `Future` could have internal references which
means it needs to never be moved.

Expand All @@ -225,7 +225,7 @@ fn foo<'a>(arg: &'a str) -> impl Future<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
lifetime. This is a big part of why the `return` type is `T` instead of `impl
Future<Output = T>`.

### "Initialization" pattern
Expand Down Expand Up @@ -305,8 +305,8 @@ costly because this exists. However, given our experience with s, we are
confident that this is the correct path forward.

There are drawbacks to several of the smaller decisions we have made as well.
There is a trade off between using the "inner" return type and the "outer"
return type, for example. We could have a different evaluation model for `async`
There is a trade off between using the "inner" `return` type and the "outer"
`return` type, for example. We could have a different evaluation model for `async`
functions in which they are evaluated immediately up to the first `await` point.
The decisions we made on each of these questions are justified in the
appropriate section of the RFC.
Expand All @@ -317,24 +317,23 @@ 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<Output = T>`)
## The `return` type (`T` instead of `impl Future<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
"interior" return type - the type that you return with the `return` keyword,
and the "exterior" return type - the type that the function returns when you
The `return` type of an asynchronous function is a sort of complicated question.
There are two different perspectives on the `return` type of an `async fn`: the
"interior" `return` type - the type that you `return` with the `return` keyword,
and the "exterior" `return` type - the type that the function returns when you
call it.

in the function signature. This RFC proposes instead to display the "inner"
Most statically typed languages with `async fn`s display the "outer" return type
return type in the function signature. This has both advantages and
disadvantages.
Most statically typed languages with `async fn`s display the "outer" `return`
type in the function signature. This has both advantages and disadvantages.

### The lifetime elision problem

As eluded to previously, the returned `Future` captures all input lifetimes. By
default, `impl Trait` does not capture any lifetimes. To accurately reflect the
outer return type, it would become necessary to eliminate lifetime elision:
outer `return` type, it would become necessary to eliminate lifetime elision:

```rust
async fn foo<'ret, 'a: 'ret, 'b: 'ret>(x: &'a i32, y: &'b i32) -> impl Future<Output = i32> + 'ret {
Expand All @@ -347,10 +346,10 @@ and much less easy to learn. This issue weighs heavily in the decision to
prefer returning the interior type.

We could have it return `impl Future` but have lifetime capture work
differently for the return type of `async fn` than other functions; this seems
differently for the `return` type of `async fn` than other functions; this seems
worse than showing the interior type.

### Polymorphic return (a non-factor for us)
### Polymorphic `return` (a non-factor for us)

According to the C# developers, one of the major factors in returning `Task<T>`
(their "outer type") was that they wanted to have `async` functions which could
Expand All @@ -363,7 +362,7 @@ return types other than `Task`. We do not have a compelling use case for this:
have `async` functions always be unboxed and only box them explicitly at the
call site. The motivation for the attribute variant was to support `async`
methods in object-safe traits. This is a special case of supporting `impl
Trait` in object-safe traits (probably by boxing the return type in the
Trait` in object-safe traits (probably by boxing the `return` type in the
object case), a feature we want separately from `async fn`.
3. It has been proposed that we support `async fn` which return `Stream`s.
However, this mean that the semantics of the internal function would differ
Expand All @@ -377,15 +376,15 @@ to return the outer type.
### Learnability / documentation trade off

There are arguments from learnability in favor of both the outer and inner
return type. One of the most compelling arguments in favor of the outer return
type is documentation: when you read automatically generated API docs, you will
definitely see what you get as the caller. In contrast, it can be easier to
understand how to write an `async` function using the inner return type, because
of the correspondence between the return type and the type of the expressions
you `return`.

Rustdoc can handle `async` functions using the inner return type in a couple of
ways to make them easier to understand. At minimum we should make sure to
`return` type. One of the most compelling arguments in favor of the outer
`return` type is documentation: when you read automatically generated API docs,
you will definitely see what you get as the caller. In contrast, it can be
easier to understand how to write an `async` function using the inner `return`
type, because of the correspondence between the `return` type and the type of the
expressions you `return`.

Rustdoc can handle `async` functions using the inner `return` type in a couple
of ways to make them easier to understand. At minimum we should make sure to
include the `async` annotation in the documentation, so that users who
understand `async` notation know that the function will return a `Future`.
We can also perform other transformations, possibly optionally, to display the
Expand Down Expand Up @@ -423,7 +422,7 @@ creating generators. In this design, we would write a generator like this:
async fn foo(arg: Arg) -> Return yield Yield
```

Both return and yield would be optional, default to `()`. An `async fn` that
Both `return` and `yield` would be optional, default to `()`. An `async fn` that
yields `()` would implement `Future`, using a blanket impl. An `async fn` that
returns `()` would implement `Iterator`.

Expand Down Expand Up @@ -530,9 +529,9 @@ green-threading).

One way in which our handling of `async`/`await` differs from most other
statically typed languages (such as C#) is that we have chosen to show the
"inner" return type, rather than the outer return type. As discussed in the
"inner" `return` type, rather than the outer `return` type. As discussed in the
alternatives section, Rust's specific context (lifetime elision, the lack of a
need for return type polymorphism here) make this deviation well-motivated.
need for `return` type polymorphism here) make this deviation well-motivated.

# Unresolved questions
[unresolved]: #unresolved-questions
Expand Down

0 comments on commit d84fb67

Please sign in to comment.