-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP: okay, just introducing this is ridiculously hard 😅
- Loading branch information
1 parent
814920a
commit 3da45e7
Showing
3 changed files
with
185 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
## Tasks | ||
## Futures and the Async-Await Syntax | ||
|
||
### Tasks | ||
|
||
As we saw in the previous chapter, threads provide one approach to concurrency, | ||
and they let us solve some of these issues. However, they also have some | ||
|
@@ -9,7 +11,9 @@ multiple threads. While mainstream desktop and mobile operating systems have all | |
had threading for many years, many embedded operating systems used on | ||
microcontrollers do not. | ||
|
||
The async-await model provides a different, complementary set of tradeoffs. | ||
The async-await model provides a different, complementary set of tradeoffs. In | ||
|
||
<!-- TODO: the following paragraph is not where it needs to be structurally. --> | ||
|
||
In the async-await model, concurrent operations do not require their own | ||
threads. Instead, they can run on *tasks*. A task is a bit like a thread, but | ||
|
@@ -19,3 +23,148 @@ Erlang, and Swift, ship runtimes with the language. In Rust, there are many | |
different runtimes, because the things a runtime for a high-throughput web | ||
server should do are very different from the things a runtime for a | ||
microcontroller should do. | ||
|
||
<!-- TODO: connective tissue as it were. --> | ||
|
||
### | ||
|
||
Like many other languages with first-class support for the async-await | ||
programming model, Rust uses the `async` and `await` keywords—though with some | ||
important differences from other languages like C# or JavaScript. Blocks and | ||
functions can be marked `async`, and you can wait on the result of an `async` | ||
function or block to resolve using the `await` keyword. | ||
|
||
Let’s write our first async function: | ||
|
||
```rust | ||
fn main() { | ||
hello_async(); | ||
} | ||
|
||
async fn hello_async() { | ||
println!("Hello, world!"); | ||
} | ||
``` | ||
|
||
If we compile and run this… nothing happens, and we get a compiler warning: | ||
|
||
```console | ||
$ cargo run | ||
warning: unused implementer of `Future` that must be used | ||
--> src/main.rs:2:5 | ||
| | ||
2 | hello_async(); | ||
| ^^^^^^^^^^^^^ | ||
| | ||
= note: futures do nothing unless you `.await` or poll them | ||
= note: `#[warn(unused_must_use)]` on by default | ||
|
||
warning: `hello-async` (bin "hello-async") generated 1 warning | ||
Finished dev [unoptimized + debuginfo] target(s) in 1.50s | ||
Running `target/debug/hello-async` | ||
``` | ||
|
||
The warning tells us why nothing happened. Calling `hello_async()` itself was | ||
not enough: we need to `.await`or poll the “future” it returns. That might be a | ||
bit surprising: we did not write a return type on the function. However, we | ||
*did* mark it as an `async fn`. In Rust, `async fn` is equivalent to writing a | ||
function which returns a *future* of the return type, using the `impl Trait` | ||
syntax we discussed back in the [“Traits as Parameters”][impl-trait] section in | ||
Chapter 10. So these two are equivalent: | ||
|
||
<!-- no-compile --> | ||
```rust | ||
fn hello_async() -> impl Future<Output = ()> { | ||
println!("Hello, async!"); | ||
} | ||
``` | ||
|
||
```rust | ||
async fn hello_async() { | ||
println!("Hello, async!"); | ||
} | ||
``` | ||
|
||
That explains why we got the `unused_must_use` warning. The other part of the | ||
warning was the note that we need to `.await` or poll the future. Rust's `await` | ||
keyword is a postfix keyword, meaning it goes *after* the expression you are | ||
awaiting. (As of now, `await` is the only postfix keyword in the language.) | ||
Let’s try that here: | ||
|
||
```rust | ||
fn main() { | ||
hello_async().await; | ||
} | ||
``` | ||
|
||
Now we actually have a compiler error! | ||
|
||
```text | ||
error[E0728]: `await` is only allowed inside `async` functions and blocks | ||
--> src/main.rs:2:19 | ||
| | ||
1 | fn main() { | ||
| ---- this is not `async` | ||
2 | hello_async().await; | ||
| ^^^^^ only allowed inside `async` functions and blocks | ||
``` | ||
|
||
Okay, so we cannot actually use `.await` in `main`, because it is not an `async` | ||
function itself—and it cannot be. To understand why, we need to pause to see | ||
what a `Future` actually is and why it needs to be `.await`-ed or polled to do | ||
anything. | ||
|
||
### Understanding `Future` | ||
|
||
Since `async fn` compiles to a return type with `impl Future<Output = …>`, we | ||
know that `Future` is a trait, with an associated type `Output`. The other part | ||
of the trait is its one method: `poll`. | ||
|
||
<!-- TODO --> | ||
|
||
The other thing to notice here is that futures in Rust are *lazy*. They do not | ||
do anything until you explicitly ask them to—whether by calling `poll` or by | ||
using `.await` to do so. | ||
|
||
### Running Async Code | ||
|
||
<!-- runtime and executor --> | ||
|
||
Going back to `main`, this explains why we cannot have an `async fn main`: what | ||
would execute the async code? We need to pick a runtime and executor. We can get | ||
started with that easily by using the simple one that comes bundled with the | ||
`futures` crate, an official home for Rust experimentation for async code. Since | ||
we will be using a bunch of tools from that crate for the rest of the chapter, | ||
let’s go ahead and add it to the dependencies for our test project: | ||
|
||
``` | ||
cargo add [email protected] | ||
``` | ||
|
||
Now we can use the executor which comes with `futures` to run the code. The | ||
`futures::executor::block_on` function takes in a `Future` and runs it until it | ||
completes, one way or another. | ||
|
||
```rust | ||
use futures::executor; | ||
|
||
fn main() { | ||
executor::block_on(hello_async()); | ||
} | ||
|
||
async fn hello_async() { | ||
println!("Hello, world!"); | ||
} | ||
``` | ||
|
||
Now when we run this, we get the behavior we might have expected initially: | ||
|
||
```console | ||
$ cargo run | ||
Compiling hello-async v0.1.0 (/Users/chris/dev/chriskrycho/async-trpl-fun/hello-async) | ||
Finished dev [unoptimized + debuginfo] target(s) in 4.89s | ||
Running `target/debug/hello-async` | ||
Hello, world! | ||
``` | ||
|
||
[impl-trait]: ch10-02-traits.html#traits-as-parameters |