Skip to content

Workarounds to Know and Love #36

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

Merged
merged 3 commits into from
Sep 5, 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
10 changes: 10 additions & 0 deletions examples/07_05_recursion/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "07_05_recursion"
version = "0.1.0"
authors = ["Taylor Cramer <[email protected]>"]
edition = "2018"

[lib]

[dev-dependencies]
futures-preview = { version = "=0.3.0-alpha.17", features = ["async-await", "nightly"] }
14 changes: 14 additions & 0 deletions examples/07_05_recursion/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![cfg(test)]
#![feature(async_await)]
#![allow(dead_code)]

// ANCHOR: example
use futures::future::{BoxFuture, FutureExt};

fn recursive() -> BoxFuture<'static, ()> {
async move {
recursive().await;
recursive().await;
}.boxed()
}
// ANCHOR_END: example
1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ members = [
"05_02_iteration_and_concurrency",
"06_02_join",
"06_03_select",
"07_05_recursion",
]
2 changes: 1 addition & 1 deletion src/01_getting_started/02_why_async.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Why Async?
# Why Async?

We all love how Rust allows us to write fast, safe software. But why write
asynchronous code?
Expand Down
2 changes: 1 addition & 1 deletion src/01_getting_started/03_state_of_async_rust.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## The State of Asynchronous Rust
# The State of Asynchronous Rust

The asynchronous Rust ecosystem has undergone a lot of evolution over time,
so it can be hard to know what tools to use, what libraries to invest in,
Expand Down
1 change: 0 additions & 1 deletion src/01_getting_started/05_http_server_example.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

Let's use `async`/`.await` to build an echo server!


To start, run `rustup update nightly` to make sure you've got the latest and
greatest copy of Rust-- we're working with bleeding-edge features, so it's
essential to stay up-to-date. Once you've done that, run
Expand Down
2 changes: 2 additions & 0 deletions src/06_multiple_futures/01_chapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ the current task until a particular `Future` completes. However, real
asynchronous applications often need to execute several different
operations concurrently.

# Executing Multiple Futures at a Time

In this chapter, we'll cover some ways to execute multiple asynchronous
operations at the same time:

Expand Down
2 changes: 2 additions & 0 deletions src/06_multiple_futures/02_join.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
The `futures::join` macro makes it possible to wait for multiple different
futures to complete while executing them all concurrently.

# `join!`

When performing multiple asynchronous operations, it's tempting to simply
`.await` them in a series:

Expand Down
6 changes: 6 additions & 0 deletions src/07_workarounds/01_chapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Workarounds to Know and Love

Rust's `async` support is still fairly new, and there are a handful of
highly-requested features still under active development, as well
as some subpar diagnostics. This chapter will discuss some common pain
points and explain how to work around them.
72 changes: 72 additions & 0 deletions src/07_workarounds/02_return_type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Return Type Errors

In a typical Rust function, returning a value of the wrong type will result
in an error that looks something like this:

```
error[E0308]: mismatched types
--> src/main.rs:2:12
|
1 | fn foo() {
| - expected `()` because of default return type
2 | return "foo"
| ^^^^^ expected (), found reference
|
= note: expected type `()`
found type `&'static str`
```

However, the current `async fn` support doesn't know to "trust" the return
type written in the function signature, causing mismatched or even
reversed-sounding errors. For example, the function
`async fn foo() { "foo" }` results in this error:

```
error[E0271]: type mismatch resolving `<impl std::future::Future as std::future::Future>::Output == ()`
--> src/lib.rs:1:16
|
1 | async fn foo() {
| ^ expected &str, found ()
|
= note: expected type `&str`
found type `()`
= note: the return type of a function must have a statically known size
```

The error says that it *expected* `&str` and found `()`,
which is actually the exact opposite of what you'd want. This is because the
compiler is incorrectly trusting the function body to return the correct type.

The workaround for this issue is to recognize that errors pointing to the
function signature with the message "expected `SomeType`, found `OtherType`"
usually indicate that one or more return sites are incorrect.

A fix to this issue is being tracked in [this bug](https://github.com/rust-lang/rust/issues/54326).

## `Box<dyn Trait>`

Similarly, because the return type from the function signature is not
propagated down correctly, values returned from `async fn` aren't correctly
coerced to their expected type.

In practice, this means that returning `Box<dyn Trait>` objects from an
`async fn` requires manually `as`-casting from `Box<MyType>` to
`Box<dyn Trait>`.

This code will result in an error:

```
async fn x() -> Box<dyn std::fmt::Display> {
Box::new("foo")
}
```

This issue can be worked around by manually casting using `as`:

```
async fn x() -> Box<dyn std::fmt::Display> {
Box::new("foo") as Box<dyn std::fmt::Display>
}
```

A fix to this issue is being tracked in [this bug](https://github.com/rust-lang/rust/issues/60424).
42 changes: 42 additions & 0 deletions src/07_workarounds/03_err_in_async_blocks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# `?` in `async` Blocks

Just as in `async fn`, it's common to use `?` inside `async` blocks.
However, the return type of `async` blocks isn't explicitly stated.
This can cause the compiler to fail to infer the error type of the
`async` block.

For example, this code:

```rust
let fut = async {
foo().await?;
bar().await?;
Ok(())
};
```

will trigger this error:

```
error[E0282]: type annotations needed
--> src/main.rs:5:9
|
4 | let fut = async {
| --- consider giving `fut` a type
5 | foo().await?;
| ^^^^^^^^^^^^ cannot infer type
```

Unfortunately, there's currently no way to "give `fut` a type", nor a way
to explicitly specify the return type of an `async` block.
To work around this, use the "turbofish" operator to supply the success and
error types for the `async` block:

```rust
let fut = async {
foo().await?;
bar().await?;
Ok::<(), MyError>(()) // <- note the explicit type annotation here
};
```

89 changes: 89 additions & 0 deletions src/07_workarounds/04_send_approximation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# `Send` Approximation

Some `async fn` state machines are safe to be sent across threads, while
others are not. Whether or not an `async fn` `Future` is `Send` is determined
by whether a non-`Send` type is held across an `.await` point. The compiler
does its best to approximate when values may be held across an `.await`
point, but this analysis is too conservative in a number of places today.

For example, consider a simple non-`Send` type, perhaps a type
which contains an `Rc`:

```rust
use std::rc::Rc;

#[derive(Default)]
struct NotSend(Rc<()>);
```

Variables of type `NotSend` can briefly appear as temporaries in `async fn`s
even when the resulting `Future` type returned by the `async fn` must be `Send`:

```rust
async fn bar() {}
async fn foo() {
NotSend::default();
bar().await;
}

fn require_send(_: impl Send) {}

fn main() {
require_send(foo());
}
```

However, if we change `foo` to store `NotSend` in a variable, this example no
longer compiles:

```rust
async fn foo() {
let x = NotSend::default();
bar().await;
}
```

```
error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely
--> src/main.rs:15:5
|
15 | require_send(foo());
| ^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be sent between threads safely
|
= help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`
= note: required because it appears within the type `NotSend`
= note: required because it appears within the type `{NotSend, impl std::future::Future, ()}`
= note: required because it appears within the type `[static generator@src/main.rs:7:16: 10:2 {NotSend, impl std::future::Future, ()}]`
= note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:7:16: 10:2 {NotSend, impl std::future::Future, ()}]>`
= note: required because it appears within the type `impl std::future::Future`
= note: required because it appears within the type `impl std::future::Future`
note: required by `require_send`
--> src/main.rs:12:1
|
12 | fn require_send(_: impl Send) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
```

This error is correct. If we store `x` into a variable, it won't be dropped
until after the `.await`, at which point the `async fn` may be running on
a different thread. Since `Rc` is not `Send`, allowing it to travel across
threads would be unsound. One simple solution to this would be to `drop`
the `Rc` before the `.await`, but unfortunately that does not work today.

In order to successfully work around this issue, you may have to introduce
a block scope encapsulating any non-`Send` variables. This makes it easier
for the compiler to tell that these variables do not live across an
`.await` point.

```rust
async fn foo() {
{
let x = NotSend::default();
}
bar().await;
}
```
53 changes: 53 additions & 0 deletions src/07_workarounds/05_recursion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Recursion

Internally, `async fn` creates a state machine type containing each
sub-`Future` being `.await`ed. This makes recursive `async fn`s a little
tricky, since the resulting state machine type has to contain itself:

```rust
// This function:
async fn foo() {
step_one().await;
step_two().await;
}
// generates a type like this:
enum Foo {
First(StepOne),
Second(StepTwo),
}

// So this function:
async fn recursive() {
recursive().await;
recursive().await;
}

// generates a type like this:
enum Recursive {
First(Recursive),
Second(Recursive),
}
```

This won't work-- we've created an infinitely-sized type!
The compiler will complain:

```
error[E0733]: recursion in an `async fn` requires boxing
--> src/lib.rs:1:22
|
1 | async fn recursive() {
| ^ an `async fn` cannot invoke itself directly
|
= note: a recursive `async fn` must be rewritten to return a boxed future.
```

In order to allow this, we have to introduce an indirection using `Box`.
Unfortunately, compiler limitations mean that just wrapping the calls to
`recursive()` in `Box::pin` isn't enough. To make this work, we have
to make `recursive` into a non-`async` function which returns a `.boxed()`
`async` block:

```rust
{{#include ../../examples/07_05_recursion/src/lib.rs:example}}
```
14 changes: 14 additions & 0 deletions src/07_workarounds/06_async_in_traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# `async` in Traits

Currently, `async fn` cannot be used in traits. The reasons for this are
somewhat complex, but there are plans to remove this restriction in the
future.

In the meantime, however, this can be worked around using the
[`async_trait` crate from crates.io](https://github.com/dtolnay/async-trait).

Note that using these trait methods will result in a heap allocation
per-function-call. This is not a significant cost for the vast majority
of applications, but should be considered when deciding whether to use
this functionality in the public API of a low-level function that is expected
to be called millions of times a second.
6 changes: 6 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
- [TODO: Spawning](404.md)
- [TODO: Cancellation and Timeouts](404.md)
- [TODO: `FuturesUnordered`](404.md)
- [Workarounds to Know and Love](07_workarounds/01_chapter.md)
- [Return Type Errors](07_workarounds/02_return_type.md)
- [`?` in `async` Blocks](07_workarounds/03_err_in_async_blocks.md)
- [`Send` Approximation](07_workarounds/04_send_approximation.md)
- [Recursion](07_workarounds/05_recursion.md)
- [`async` in Traits](07_workarounds/06_async_in_traits.md)
- [TODO: I/O](404.md)
- [TODO: `AsyncRead` and `AsyncWrite`](404.md)
- [TODO: Asynchronous Design Patterns: Solutions and Suggestions](404.md)
Expand Down