Asynchronous Programming: Async, Await, Futures, and Streams
Modern applications require handling concurrent operations efficiently. Rust provides async programming through Futures, Streams, and the async
/await
syntax. This chapter covers async fundamentals and practical patterns for building concurrent systems.
You’ll learn:
async
/await
syntax and the Future trait- Concurrent programming patterns with async
- Working with multiple futures and streams
- Trait details:
Future
,Pin
,Unpin
,Stream
- Combining async with threads
Parallelism vs Concurrency
Concurrency: Single worker switches between tasks before completion (time-slicing). Parallelism: Multiple workers execute tasks simultaneously. Serial: Tasks must complete in sequence due to dependencies.
Concurrent: A1 → B1 → A2 → B2 → A3 → B3
Parallel: A1 → A2 → A3
B1 → B2 → B3
CPU-bound operations benefit from parallelism. IO-bound operations benefit from concurrency. Most real systems need both.
In async Rust, concurrency is primary. The runtime may use parallelism underneath, but your async code provides concurrency semantics.
Blocking vs Non-blocking
Traditional IO operations block execution until completion. Async operations yield control to the runtime when waiting, allowing other tasks to progress.
// Blocking - thread stops here
let data = std::fs::read("file.txt")?;
// Non-blocking - runtime can schedule other work
let data = tokio::fs::read("file.txt").await?;
Async enables writing sequential-looking code that’s actually concurrent:
let data = fetch_data_from(url).await;
println!("{data}");
This syntax compiles to a state machine that yields control at await points.