mirror of https://github.com/rust-lang/async-book
Add chapter: Workarounds to Know and Love
This commit is contained in:
parent
2f17ce0ce3
commit
b3467d9f9f
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "07_05_recursion"
|
||||
version = "0.1.0"
|
||||
authors = ["Taylor Cramer <cramertj@google.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
|
||||
[dev-dependencies]
|
||||
futures-preview = { version = "=0.3.0-alpha.17", features = ["async-await", "nightly"] }
|
|
@ -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
|
|
@ -11,4 +11,5 @@ members = [
|
|||
"05_02_iteration_and_concurrency",
|
||||
"06_02_join",
|
||||
"06_03_select",
|
||||
"07_05_recursion",
|
||||
]
|
||||
|
|
|
@ -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.
|
|
@ -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).
|
|
@ -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
|
||||
};
|
||||
```
|
||||
|
|
@ -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 breifly 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 successfuly 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;
|
||||
}
|
||||
```
|
|
@ -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}}
|
||||
```
|
|
@ -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.
|
|
@ -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_traits.md)
|
||||
- [TODO: I/O](404.md)
|
||||
- [TODO: `AsyncRead` and `AsyncWrite`](404.md)
|
||||
- [TODO: Asynchronous Design Patterns: Solutions and Suggestions](404.md)
|
||||
|
|
Loading…
Reference in New Issue