liveBook · Manning

Async/await

As explained in Code Like a Pro in Rust

Async/await is syntax for defining and suspending asynchronous computations until awaited operations make progress.

Use `async` to define a function, closure, or block that returns a future. The future is lazy: creating it does not run its body. Within an async context, `.await` drives a future toward its result and gives the runtime opportunities to schedule other tasks. This keeps asynchronous code close to synchronous control flow without requiring direct calls to `Future::poll`. section 8.4 section 8.11

Run an awaited timer

`#[tokio::main] async fn main() { tokio::time::sleep(std::time::Duration::from_secs(1)).await; println!("Hello, world!"); }` The macro starts a Tokio runtime and supplies the async context required by `.await`. Without a runtime driving the future, merely creating the sleep future would not run it. section 8.3.1 section 8.4

#[tokio::main]
async fn main() {
   async {
       println!("This line prints first");
   }
   .await;
   let _future = async {
       println!("This line never prints");
   };
   tokio::task::spawn(async {
       println!(
           "This line prints sometimes, depending on how quick it runs"
       )
   });

   println!("This line always prints, but it may or may not be last");
}

Compare an awaited block, a future that is never awaited, and a spawned task. The unawaited block never prints; the spawned task may not print before the program exits. Spawning is not a completion guarantee—await its task handle when completion matters. section 8.4

Choose execution and concurrency deliberately:

  • Use `.await` inside async code when the current task needs a future’s result. Use `tokio::task::spawn()` to schedule a task; await its handle if the caller must know it finished. section 8.4
  • Use `tokio::select!` or `tokio::join!` for explicit concurrency, and `tokio::task::spawn_blocking()` for blocking operations rather than treating ordinary blocking work as async work. section 8.11
  • Pick a runtime alongside the crates that depend on it. Rust supplies `Future`, `async`, and `.await`, but not a prescribed runtime; mixing runtimes in one context is impractical, and Tokio is the recommended default here. section 8.1

Trait-based APIs expose what the syntax normally hides. In the implementation snapshot shown here, an async observer method is expressed as a returned future; returning `Box<dyn Future<…>>` alone fails at `.await` because the trait object is not `Unpin`. Returning `Pin<Box<dyn Future<…>>>` and constructing it with `Box::pin(async { … })` addresses that error. The stated availability of async methods in traits is time-specific, not a timeless language limitation. section 8.6

In short: start a compatible runtime, await futures you need to execute, and distinguish a spawned task from one whose completion you have confirmed. section 8.1 section 8.4

How Code Like a Pro in Rust covers it

How deep it goes
Explains lazy futures and polling, then works through spawning and a pinned-future trait workaround without providing an exhaustive language reference.
Examples
Demonstrates a Tokio sleep timer, contrasting awaited and unawaited blocks, spawned printing, and an async observer implementation.
What it assumes
Assumes Rust familiarity while relating async/await to futures and supplying the Tokio runtime setup needed to execute them.
What you can do after
Build it from scratch with Tokio: start an async context, await needed results, spawn tasks deliberately, and pin boxed futures where required.
How it connects
Connects async/await to the `Future` trait, Tokio runtimes, task scheduling, concurrency, blocking operations, and pinned trait-object futures.

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 Code Like a Pro in Rust. The section links point to the parts of the book it draws on.