Rust async functions return futures that need an executor before their .await points make progress. Tokio provides that runtime for Rust applications that need async timers, sockets, tasks, channels, or other non-blocking work inside a normal Cargo binary.
The #[tokio::main] attribute starts a Tokio runtime before main runs. A small command-line program can use that attribute, add only the Tokio features it needs, and then call async functions with .await from the entry point.
Start from an existing binary Cargo project. A timer-backed async function gives the runtime a real awaited task to drive, and cargo run --quiet should print one success line without showing build output.
$ cargo add tokio@1 --features macros,rt-multi-thread,time
tokio@1 keeps the dependency on the current Tokio 1.x line. The macros feature enables #[tokio::main], rt-multi-thread enables the default multi-thread runtime, and time enables tokio::time::sleep.
$ cat Cargo.toml
[package]
name = "async-tokio-demo"
version = "0.1.0"
edition = "2024"
[dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
Your package name, version, and edition can differ. The Tokio dependency line is the part to verify.
use tokio::time::{sleep, Duration}; async fn fetch_message() -> String { sleep(Duration::from_millis(250)).await; "async task finished".to_string() } #[tokio::main] async fn main() { let message = fetch_message().await; println!("{message}"); }
The call to fetch_message().await yields to the runtime until the timer completes, then continues inside main.
$ cargo run --quiet async task finished
If tokio::time or #[tokio::main] is not found, recheck the Tokio features in Cargo.toml before changing the Rust source.