Rust Tutorial · Smart Pointers and Concurrency · Lesson 26 of 29
Rust Async and Await
What async Rust is for, how futures and runtimes like Tokio work, and when to use async or threads.
Futures and runtimes
async fn returns a future: a value that does nothing until it's polled. A runtime such as Tokio
polls futures, running thousands of I/O-bound tasks (network requests, database calls) on a few threads.
The standard library defines futures but not a runtime, so you add one as a dependency:
// Cargo.toml: tokio = { version = "1", features = ["full"] }
use std::time::Duration;
async fn fetch_price(item: &str) -> f64 {
tokio::time::sleep(Duration::from_millis(100)).await; // stands in for a network call
if item == "mug" { 8.5 } else { 24.0 }
}
#[tokio::main]
async fn main() {
// both requests run concurrently: about 100 ms in total, not 200 ms
let (mug, teapot) = tokio::join!(fetch_price("mug"), fetch_price("teapot"));
println!("{mug} + {teapot}");
}
When to use async
- Async: many concurrent I/O operations (web servers with Axum, clients with reqwest, databases with sqlx).
- Threads (or Rayon): CPU-heavy work such as parsing or image processing.
Futures are lazy: the example below builds futures with the standard library alone and shows that nothing runs until they're awaited by an executor.
use std::future::Future;
use std::pin::pin;
use std::sync::Arc;
use std::task::{Context, Poll, Wake, Waker};
struct NoopWaker;
impl Wake for NoopWaker { fn wake(self: Arc<Self>) {} }
// A tiny executor for ready futures; real programs use a runtime such as Tokio.
fn block_on<F: Future>(future: F) -> F::Output {
let waker = Waker::from(Arc::new(NoopWaker));
let mut cx = Context::from_waker(&waker);
let mut future = pin!(future);
loop {
if let Poll::Ready(value) = future.as_mut().poll(&mut cx) { return value; }
}
}
async fn price(item: &str) -> f64 {
println!(" computing price of {item}");
if item == "mug" { 8.5 } else { 24.0 }
}
async fn total() -> f64 {
price("mug").await + price("teapot").await
}
fn main() {
let future = total();
println!("future created, nothing ran yet");
println!("total: {}", block_on(future));
}
Output
future created, nothing ran yet computing price of mug computing price of teapot total: 32.5
Run this on your computer — see “Get Started” for setup.
Test yourself
What does calling an async fn return?
The result immediatelyA future that does nothing until it's awaited or polledA new threadA callback
Show answer
Answer: A future that does nothing until it's awaited or polled