Rust by Example: Tokio Hello World
Build production-ready async applications with Tokio. Learn to use the most popular async runtime in Rust to spawn tasks, perform non-blocking I/O, and manage timers efficiently.
Code
use tokio::time::{sleep, Duration};
// The #[tokio::main] macro transforms the async main function
// into a synchronous main that initializes the runtime and calls block_on.
#[tokio::main]
async fn main() {
println!("Hello from Tokio!");
// Spawn a lightweight async task
let handle = tokio::spawn(async {
// Simulate some work
sleep(Duration::from_millis(100)).await;
"Task complete"
});
// Do other work concurrently
println!("Main task working...");
sleep(Duration::from_millis(50)).await;
println!("Main task done waiting.");
// Await the spawned task
let result = handle.await.unwrap();
println!("Got result: {}", result);
}Explanation
While Rust provides the Future trait and async/await syntax, it does not provide a runtime in the standard library. You need a community crate to actually execute async code. Tokio is the most popular and feature-rich async runtime for Rust.
Tokio provides an event loop, an I/O driver (for network/files), and a timer driver. It also provides async versions of standard library utilities, such as tokio::fs, tokio::net, and tokio::sync. Mixing synchronous blocking code (like std::thread::sleep) with async code is a common mistake; always use the Tokio equivalents (like tokio::time::sleep) to avoid blocking the runtime thread.
tokio::spawn is used to run a new async task concurrently. It is similar to thread::spawn, but the tasks are much lighter (often called "green threads"). Tokio can multiplex thousands of tasks onto a few OS threads using a work-stealing scheduler.
Code Breakdown
#[tokio::main]. This attribute macro sets up the Tokio runtime. It replaces fn main with code that builds a runtime and calls runtime.block_on(async_main).tokio::spawn. Spawns a background task. The task must be Send because it might be moved to another thread by the work-stealing scheduler. It returns a JoinHandle.sleep(...).await. We use Tokio's sleep, not std::thread::sleep. This yields the thread to other tasks instead of blocking it completely.handle.await. We await the handle to get the result of the task. If the task panicked, this would return an error.
