tezvyn:

Rust's async/await: Cooperative Concurrency

AI-drafted, machine-checkedSource: rust-lang.github.iointermediate

Rust's async/await is cooperative concurrency, where tasks explicitly yield control with `.await`. This is ideal for I/O-bound work like managing thousands of network connections. The biggest footgun: calling an `async` function without `.await` does nothing.

WHY IT EXISTS: To handle high levels of concurrency without the overhead of OS threads. Traditional threads have significant memory costs and their context-switching is slow because it involves the operating system. Async provides a lighter, faster alternative for tasks that are mostly waiting.

THE MENTAL MODEL: Think of async not as "running in the background" but as "pausable computation." An async fn returns a "future," which is a state machine representing the task. An async runtime, like Tokio, manages these futures. When you .await a future, you tell the runtime, "Pause this task until the awaited operation is complete, and run something else in the meantime." This is cooperative multitasking, managed inside your program, not by the OS.

HOW IT WORKS: You declare a function with async fn, which makes it return a future instead of its value directly. To get the value, you must use the .await keyword inside another async function. This .await point is where the function can be paused. The entire system is driven by an async runtime (a crate like tokio or async-std) which polls futures to see if they can make progress. Without a runtime and an .await, an async function call does nothing.

WHEN TO USE IT: Use async/await for I/O-bound applications. This includes network services, database clients, or any system that needs to handle many simultaneous connections where tasks spend most of their time waiting for data. It's also excellent for embedded systems and microcontrollers with limited memory where OS threads are not an option.

WHEN NOT TO USE IT: Avoid async for CPU-bound tasks that don't involve waiting. A long-running calculation in an async task will block the executor's thread, preventing other tasks from running. For heavy computation, traditional threading (like std::thread::spawn) is a better fit, as the OS can preemptively schedule threads across multiple CPU cores.

ONE CANONICAL EXAMPLE: A basic "hello, world" shows the core syntax. An async function is defined with async fn say_hello(). To run it, we need an async main function, enabled by a runtime macro like #[tokio::main]. Inside main, we call the function and pause execution until it completes using say_hello().await;. The key takeaway is that calling say_hello() alone creates a future; only .await actually runs it.

Read the original → rust-lang.github.io

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.