What is async/await in Rust and how do futures work?
Understand async/await in Rust: how futures are polled, why they are lazy, the role of runtimes and wakers, with Tokio code and interview questions answered.
Expected Interview Answer
async/await in Rust is syntax for writing non-blocking, concurrent code: an `async` function returns a `Future` — a value representing a computation that may not be finished yet — and `.await` suspends the current task until that future is ready, without blocking the underlying thread.
A Rust `Future` is lazy: it does nothing until polled. A runtime such as Tokio or async-std drives it by repeatedly calling `poll`, which returns `Poll::Pending` when the work must wait (for example on I/O) or `Poll::Ready(value)` when it completes. The compiler transforms each async function into a state machine that remembers where it suspended, and a `Waker` notifies the runtime when a pending future can make progress. This lets one thread juggle thousands of tasks efficiently.
- Non-blocking concurrency without one thread per task
- Scales to many simultaneous I/O operations
- Zero-cost state machines generated at compile time
- Futures are lazy, so no work happens until awaited
- Composes cleanly with combinators like join and select
AI Mentor Explanation
async/await is like a twelfth man handling several errands during a match: he starts fetching water, and while the tap fills he does not stand idle — he tapes a bat, then returns when the bottle is full. Each errand is a future that yields control when it must wait, so one player keeps many tasks moving instead of freezing on the first slow one.
Step-by-Step Explanation
Step 1
Mark a function async
Declaring `async fn` makes the function return an anonymous type that implements the Future trait instead of running immediately.
Step 2
Understand laziness
Calling an async function does nothing until the future is awaited or handed to a runtime; futures are inert until polled.
Step 3
Await a future
`.await` suspends the current task, yielding control to the runtime when the awaited future returns Poll::Pending.
Step 4
The runtime polls
An executor like Tokio repeatedly calls poll, advancing the compiler-generated state machine until it returns Poll::Ready.
Step 5
Wakers resume work
When a pending resource becomes ready, its Waker tells the runtime to poll that task again so it can continue.
Step 6
Compose concurrency
Use join! to run futures together or select! to race them, all on one thread without spawning OS threads per task.
What Interviewer Expects
- Futures are lazy and do nothing until polled
- Understanding of poll returning Pending or Ready
- Role of the runtime/executor (Tokio, async-std)
- How the compiler builds a state machine from async fn
- The purpose of Wakers in resuming pending tasks
- Difference between async concurrency and OS-thread parallelism
Common Mistakes
- Thinking an async function runs as soon as it is called
- Confusing async concurrency with multithreaded parallelism
- Forgetting that futures need a runtime to be driven
- Blocking inside async code with synchronous calls, stalling the executor
- Assuming .await spawns a thread rather than yielding cooperatively
Best Answer (HR Friendly)
“async/await lets a Rust program start a slow task, like a network request, and keep doing other work instead of waiting idle. The unfinished task is called a future, and a background runtime resumes it once it is ready, so one thread can handle many jobs at once.”
Code Example
use tokio::time::{sleep, Duration};
async fn fetch(id: u32) -> u32 {
sleep(Duration::from_millis(100)).await; // non-blocking wait
id * 2
}
#[tokio::main]
async fn main() {
// Both futures run concurrently on one runtime
let (a, b) = tokio::join!(fetch(1), fetch(2));
println!("results: {} {}", a, b); // 2 4
}Follow-up Questions
- What is the difference between concurrency and parallelism in async Rust?
- Why are Rust futures lazy compared to JavaScript promises?
- What does the Waker do and who calls it?
- How does the compiler turn an async fn into a state machine?
- What happens if you block a thread inside an async task?
- When would you use tokio::spawn versus join!?
MCQ Practice
1. What does an async function return in Rust?
An async fn returns an anonymous type implementing Future; it does no work until awaited or driven by a runtime.
2. What are the two variants poll can return?
Future::poll returns Poll::Pending when the work must wait or Poll::Ready(value) when it has completed.
3. What is the role of a Waker?
When a pending resource becomes ready, its Waker signals the executor to poll the task again so it can continue.
Flash Cards
What does async fn return? — A value implementing the Future trait that is lazy until polled by a runtime.
What does .await do? — Suspends the current task, yielding to the runtime when the awaited future is Pending, then resumes with its value.
What does poll return? — Poll::Pending if the future must wait, or Poll::Ready(value) when it has completed.
What is a Waker? — A handle that notifies the executor a pending future is ready to be polled again.
Continue Learning
Related Interview Questions
What is cancellation safety in Rust async, and how does select! lose data?
hard
Why does tokio::spawn require Send + 'static, and how do you satisfy it without cloning everything?
hard
How do you return a future from a trait method in Rust, and what are the trade-offs?
hard
What is the difference between fearless concurrency and data races in Rust?
hard