diff --git a/examples/07_05_recursion/Cargo.toml b/examples/07_05_recursion/Cargo.toml new file mode 100644 index 00000000..b70e7acb --- /dev/null +++ b/examples/07_05_recursion/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "07_05_recursion" +version = "0.1.0" +authors = ["Taylor Cramer "] +edition = "2018" + +[lib] + +[dev-dependencies] +futures-preview = { version = "=0.3.0-alpha.17", features = ["async-await", "nightly"] } diff --git a/examples/07_05_recursion/src/lib.rs b/examples/07_05_recursion/src/lib.rs new file mode 100644 index 00000000..36dbd386 --- /dev/null +++ b/examples/07_05_recursion/src/lib.rs @@ -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 diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 230ab300..b89b3115 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -11,4 +11,5 @@ members = [ "05_02_iteration_and_concurrency", "06_02_join", "06_03_select", + "07_05_recursion", ] diff --git a/src/01_getting_started/02_why_async.md b/src/01_getting_started/02_why_async.md index b095514e..94e6c4ce 100644 --- a/src/01_getting_started/02_why_async.md +++ b/src/01_getting_started/02_why_async.md @@ -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? diff --git a/src/01_getting_started/03_state_of_async_rust.md b/src/01_getting_started/03_state_of_async_rust.md index 87cb299f..7bdfa978 100644 --- a/src/01_getting_started/03_state_of_async_rust.md +++ b/src/01_getting_started/03_state_of_async_rust.md @@ -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, diff --git a/src/01_getting_started/05_http_server_example.md b/src/01_getting_started/05_http_server_example.md index 5b9a98ee..3b0a9004 100644 --- a/src/01_getting_started/05_http_server_example.md +++ b/src/01_getting_started/05_http_server_example.md @@ -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 diff --git a/src/06_multiple_futures/01_chapter.md b/src/06_multiple_futures/01_chapter.md index 4295b210..1a783ee2 100644 --- a/src/06_multiple_futures/01_chapter.md +++ b/src/06_multiple_futures/01_chapter.md @@ -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: diff --git a/src/06_multiple_futures/02_join.md b/src/06_multiple_futures/02_join.md index 8c6e07ab..3d5c3817 100644 --- a/src/06_multiple_futures/02_join.md +++ b/src/06_multiple_futures/02_join.md @@ -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: diff --git a/src/07_workarounds/01_chapter.md b/src/07_workarounds/01_chapter.md new file mode 100644 index 00000000..6b03babf --- /dev/null +++ b/src/07_workarounds/01_chapter.md @@ -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. diff --git a/src/07_workarounds/02_return_type.md b/src/07_workarounds/02_return_type.md new file mode 100644 index 00000000..4a2a795e --- /dev/null +++ b/src/07_workarounds/02_return_type.md @@ -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 `::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` + +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` objects from an +`async fn` requires manually `as`-casting from `Box` to +`Box`. + +This code will result in an error: + +``` +async fn x() -> Box { + Box::new("foo") +} +``` + +This issue can be worked around by manually casting using `as`: + +``` +async fn x() -> Box { + Box::new("foo") as Box +} +``` + +A fix to this issue is being tracked in [this bug](https://github.com/rust-lang/rust/issues/60424). diff --git a/src/07_workarounds/03_err_in_async_blocks.md b/src/07_workarounds/03_err_in_async_blocks.md new file mode 100644 index 00000000..9bf36759 --- /dev/null +++ b/src/07_workarounds/03_err_in_async_blocks.md @@ -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 +}; +``` + diff --git a/src/07_workarounds/04_send_approximation.md b/src/07_workarounds/04_send_approximation.md new file mode 100644 index 00000000..fd8bcb73 --- /dev/null +++ b/src/07_workarounds/04_send_approximation.md @@ -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; +} +``` diff --git a/src/07_workarounds/05_recursion.md b/src/07_workarounds/05_recursion.md new file mode 100644 index 00000000..84d3ee2e --- /dev/null +++ b/src/07_workarounds/05_recursion.md @@ -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}} +``` diff --git a/src/07_workarounds/06_async_in_traits.md b/src/07_workarounds/06_async_in_traits.md new file mode 100644 index 00000000..4ac0ad28 --- /dev/null +++ b/src/07_workarounds/06_async_in_traits.md @@ -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. diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 9f4a2388..92f22f0f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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)