the hidden cost of async rust
async is not free
rust's async/await looks like magic. you write code that looks synchronous, and the compiler transforms it into a state machine that runs cooperatively on a thread pool.
but async is not free. it has costs that are easy to miss.
future sizes
every async fn compiles into a state machine struct. the size of this struct is the size of all the variables that must be alive across an .await point.
async fn fetch_and_process(url: String) -> Result<()> {
let body = reqwest::get(&url).await?.text().await?; // large future
process(body).await
}
a future holding a large buffer across an await will put that entire buffer on the "stack" of the executor's task. with thousands of concurrent tasks, this adds up.
cargo +nightly build -Z print-type-sizes 2>&1 | grep "Future"
the cost of spawning
tokio::spawn allocates a task on the heap. that allocation has a cost. spawning thousands of small tasks is faster than threads, but it's not free.
prefer structured concurrency with join! or FuturesUnordered over spawning individual tasks when you don't need true parallelism.
blocking in async context
the worst mistake: calling a blocking function inside an async context.
// BAD: blocks the executor thread
async fn bad() {
std::thread::sleep(Duration::from_secs(1)); // blocks entire thread
}
// GOOD: yields to executor
async fn good() {
tokio::time::sleep(Duration::from_secs(1)).await;
}
one blocking call in an async context can stall every other task sharing that executor thread.