Lesson 22 / 25

Async Rust at a Glance

Futures, executors and await.

Many tasks, few threads

For I/O-heavy servers handling thousands of connections, async Rust uses async fn and .await to write non-blocking code. Futures do nothing until polled by an executor (runtime) such as Tokio, which schedules many tasks on a small pool of threads. Frameworks like Axum and Actix Web build on it. Async adds complexity (pinning, Send bounds across awaits), so use threads for CPU-bound work and async for high-concurrency I/O.

An async HTTP handler with Tokio and Axum (sketch)

Needs the tokio and axum crates; not run here because these examples use only the standard library offline.

use axum::{routing::get, Router};

async fn health() -> &'static str {
    "ok"
}

#[tokio::main]
async fn main() {
    let app = Router::new().route("/health", get(health));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Do not block inside async tasks

CPU-heavy or blocking calls inside async code stall the runtime; move them to spawn_blocking or a thread.

Quick check: What runs async futures in Rust?

  • Nothing; futures run when created
  • The compiler at build time
  • The operating system automatically
  • An executor (runtime) such as Tokio
Answer

An executor (runtime) such as Tokio — Futures are lazy until polled.