liveBook · Manning

Async/await

As explained in Learn Rust in a Month of Lunches

async is a Rust language construct for defining computations that produce futures and can suspend while awaiting asynchronous results.

When a program waits for a server to respond, ordinary code blocks its thread. Async Rust lets other work proceed during that wait. An `async fn` creates a future: a computation whose result may be available now or later. Its declared return type looks ordinary, but calling `async fn give_8() -> u8` gives you an `impl Future<Output = u8>`, not a `u8` yet. section 19.3 section 19.3.1

Add `.await` to obtain the future’s output. You can use `.await` only inside an async function or block. A runtime handles checking whether the future is ready; you do not need to call `poll` yourself. If it is pending, the runtime can return to it later. Don’t worry about the deeper details of `Pin` to get started. section 19.3.1 section 19.3.2

Await a value in main

```rust async fn give_8() -> u8 { 8 } #[tokio::main] async fn main() { let number = give_8().await; println!("{number}"); } ``` Trying `.await` in a regular `main` produces a compiler error; changing it to `async fn main` alone produces another. `#[tokio::main]` supplies the Tokio runtime that runs the async body to completion. section 19.3.2 section 19.3.3

A few practical consequences follow:

  • A regular function cannot await an async function, so a caller that needs its result may need to become async too. An async function can call a regular function, but that call can still block the thread. section 19.3.3
  • Awaiting several futures one after another finishes each before starting the next. Tokio’s `join!` polls them together; `select!` takes the first completion, and `try_join!` handles futures that return results. section 19.3.4 section 19.4
  • With reqwest’s async client, await both sending a request and reading the response text. An Axum route can use an async handler, and its server’s `.serve()` call is awaited. section 19.3.3 section 24.1.1
use rand::*;
use tokio::join;
use std::time::Duration;
 
async fn wait_and_give_u8(num: u8) -> u8 {
    let mut rng = rand::thread_rng();
    let wait_time = rng.gen_range(1..100);
    tokio::time::sleep(Duration::from_millis(wait_time)).await;
    println!("Got a number! {num}");
    num
}
 
#[tokio::main]
async fn main() {
 
    let nums = join!(
        wait_and_give_u8(1),
        wait_and_give_u8(2),
        wait_and_give_u8(3)
    );
 
    println!("{nums:?}");
}

Three futures each take a short, randomly timed nonblocking sleep. `join!` polls them together, so their completion messages may appear in a different order even though the returned tuple stays ordered. section 19.3.4

How Learn Rust in a Month of Lunches covers it

How deep it goes
Explains futures, awaiting, polling states, runtime setup, and compiler errors; defers the deeper reasons for `Pin`.
Examples
Shows compiler errors for awaiting in `main`, a Tokio-powered fix, an async HTTP request, and timed futures polled together.
What it assumes
Introduces futures and polling from basics while building on Rust functions and crate-based examples.
What you can do after
Apply it via Tokio to await async results and use `join!` when several futures should be polled together.
How it connects
Connects async/await to `Future`, `Poll`, Tokio runtimes, reqwest, `join!`, and `select!`.

Also explained in

Compare how 17 books explain async/await →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Rust in a Month of Lunches. The section links point to the parts of the book it draws on.