Step 1 of 7
Futures, async fn and .await
Threads are great for keeping CPU cores busy. But a lot of real work is waiting: for a database query, a web API, a file, a timer. A web server with 10,000 open connections spends almost all of its time waiting for the network. One OS thread per connection means 10,000 threads, each with its own stack, and the operating system constantly switching between them.
Async code lets a few threads juggle thousands of waiting jobs. Whenever a job would wait, it steps aside, and the thread runs another job that's ready to make progress. Two keywords make it work:
async fndefines a function that doesn't run when you call it. Instead, calling it returns a future: a value that stands for a computation that will produce its result later. (Formally, something that implements theFuturetrait, with anOutputtype.).await, written after a future, runs it until it finishes and gives you its output. While the future is waiting,.awaitlets the current job pause so other jobs can run.
async fn fetch_price(item: &str) -> u32 {
match item {
"kelp" => 3,
"pearl" => 250,
_ => 0,
}
}
#[tokio::main]
async fn main() {
let price = fetch_price("pearl").await;
println!("pearl costs {price}");
let total = fetch_price("kelp").await * 4;
println!("four kelp cost {total}");
}
pearl costs 250
four kelp cost 12
async fn fetch_price(item: &str) -> u32really returnsimpl Future<Output = u32>. In a real program it would call a web service; here it answers at once, so you can focus on the syntax.fetch_price("pearl").awaitruns the future and gives back theu32. Because.awaitis postfix, calls chain nicely:fetch(url).await?.parse().#[tokio::main]sets up an async runtime forasync fn main. More on that below.
Futures are lazy
Calling an async function does nothing until the future is awaited:
async fn load_profile(name: &str) -> String {
println!("loading {name}...");
format!("{name} (level 12)")
}
#[tokio::main]
async fn main() {
let future = load_profile("ferris");
println!("future created, nothing loaded yet");
let profile = future.await;
println!("got {profile}");
}
future created, nothing loaded yet
loading ferris...
got ferris (level 12)
Forget the .await and the work never happens. The compiler warns you: warning: unused implementer of `Future` that must be used, with the note futures do nothing unless you .await or poll them. That warning almost always means a bug.
The runtime
Rust itself gives you async, .await and the Future trait (in the standard library), but not the machinery that runs futures, the runtime (also called an executor). You pick one as a crate. Tokio is by far the most widely used, and most async crates (the axum web framework, the reqwest HTTP client, the sqlx database library) are built on it:
[dependencies]
tokio = { version = "1", features = ["full"] }
#[tokio::main] turns async fn main into a normal main that starts a Tokio runtime and runs your code on it. Without it, rustc stops you with error[E0752]: `main` function is not allowed to be `async` .
.await only works in async code
async fn price() -> u32 {
5
}
fn main() {
let p = price().await;
println!("{p}");
}
error[E0728]: `await` is only allowed inside `async` functions and blocks
The fix is to make the calling function async too, and its callers, all the way up to main. That's normal: async code spreads up the call chain.
Common mistakes
- Forgetting
.await: the future is created and thrown away; nothing runs. Read the warning. - Writing
await fetch()as in JavaScript or C#. Rust's.awaitgoes after:fetch().await. - Using
.awaitin a normalfn:E0728. Make the functionasync, or (in tests and small tools) run the future with a runtime'sblock_on, next step.
Your task
Fill in the three blanks: the attribute that starts Tokio, the keyword that makes main asynchronous, and the keyword that waits for the first future. The program should print in stock: 10 items.
Challenge 2
double_score is awaited in main, so it has to be an async function. Fill in the keyword. The program should print score: 42.
Challenge 3
Write two async functions and use them from main:
async fn load_scores(player: &str) -> Vec<u32>returns[30, 50, 20]for"ana",[80, 10]for"bo", and an emptyVecfor anyone else.async fn best_score(player: &str) -> Option<u32>awaitsload_scoresand returns the highest score (Noneif there are no scores).
In main, print the best score of ana, bo and cy:
ana: Some(50)
bo: Some(80)
cy: None