ScriptsApr 12, 2026·2 min read

Tokio — Async Runtime for Reliable Rust Applications

Tokio is a runtime for writing reliable asynchronous applications with Rust. Provides async I/O, networking, scheduling, and timers. The foundation of most async Rust projects including Axum, Hyper, Tonic, and the broader Rust web ecosystem.

TL;DR
Tokio provides async I/O, task scheduling, and networking primitives that power most Rust web frameworks.
§01

What it is

Tokio is a runtime for writing reliable asynchronous applications with Rust. It provides async I/O, networking, scheduling, timers, and synchronization primitives. Frameworks like Axum, Hyper, and Tonic build on top of Tokio, making it the foundation of the Rust async ecosystem.

Tokio targets backend engineers who need high-throughput, low-latency network services. If you write HTTP servers, gRPC services, or database drivers in Rust, you almost certainly depend on Tokio.

§02

How it saves time or tokens

Tokio replaces hand-rolled thread pools and event loops with a well-tested, work-stealing scheduler. Instead of managing OS threads manually, you spawn lightweight tasks that Tokio multiplexes across a small thread pool. A single Tokio runtime can handle tens of thousands of concurrent connections on commodity hardware. The ecosystem of compatible crates (reqwest, sqlx, tonic) means you rarely write low-level networking code.

§03

How to use

  1. Add Tokio to your Cargo.toml:
[dependencies]
tokio = { version = "1", features = ["full"] }
  1. Write an async main function:
#[tokio::main]
async fn main() {
    println!("Hello from Tokio");
}
  1. Spawn concurrent tasks:
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        sleep(Duration::from_secs(1)).await;
        42
    });
    let result = handle.await.unwrap();
    println!("Got: {result}");
}
§04

Example

A minimal TCP echo server using Tokio:

use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = TcpListener::bind("127.0.0.1:8080").await?;
    loop {
        let (mut socket, _) = listener.accept().await?;
        tokio::spawn(async move {
            let mut buf = [0; 1024];
            loop {
                let n = socket.read(&mut buf).await.unwrap();
                if n == 0 { return; }
                socket.write_all(&buf[..n]).await.unwrap();
            }
        });
    }
}
§05

Related on TokRepo

§06

Common pitfalls

  • Calling blocking code (std::fs, heavy computation) inside async tasks starves the runtime. Use tokio::task::spawn_blocking for CPU-bound or blocking operations.
  • Forgetting to enable the macros feature flag means #[tokio::main] and #[tokio::test] will not compile.
  • Holding a MutexGuard across an .await point causes deadlocks. Use tokio::sync::Mutex or restructure to drop the guard before awaiting.

Frequently Asked Questions

What is the difference between Tokio and async-std?+

Both are async runtimes for Rust. Tokio uses a work-stealing scheduler and has a larger ecosystem of compatible crates (Axum, Hyper, Tonic). async-std follows the standard library API more closely. Most production Rust projects choose Tokio due to ecosystem breadth.

Do I need Tokio if I use Axum?+

Yes. Axum is built on top of Tokio and requires the Tokio runtime. When you add Axum as a dependency, Tokio is pulled in transitively, but you still need `#[tokio::main]` on your entry point.

How many concurrent connections can Tokio handle?+

Tokio can handle tens of thousands of concurrent connections on a single machine. Each connection is a lightweight task, not an OS thread. The actual limit depends on available memory, file descriptor limits, and application logic.

Can I use Tokio for CPU-bound work?+

Tokio is optimized for I/O-bound workloads. For CPU-bound tasks, use `tokio::task::spawn_blocking` to offload work to a dedicated thread pool, keeping the async scheduler responsive for I/O tasks.

Does Tokio support single-threaded mode?+

Yes. Use `#[tokio::main(flavor = "current_thread")]` for a single-threaded runtime. This is useful for embedded environments, WASM targets, or when you want deterministic task ordering.

Citations (3)

Discussion

Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.

Related Assets