Introduction to Asynchronous Programming in Rust
Asynchronous programming is an approach that allows your application to efficiently perform multiple tasks simultaneously without blocking the main execution thread. In Rust, this approach is implemented through the async/await model, which provides the memory safety and high performance characteristic of the language.
Unlike traditional multithreading, asynchronous code runs within one or several threads, switching between tasks during waiting periods (for example, I/O or network requests). This makes it ideal for creating high-load web servers, network applications, and data processing tools.
In this article, we will break down the basics of asynchronous programming in Rust: from key concepts to practical examples. You will learn how Future, async, and await work, get acquainted with the popular runtime Tokio, and write your first asynchronous project.
Core Concepts: Future, async, and await
What is a Future?
In Rust, an asynchronous task is represented by the Future trait. A Future is a value that may not be ready yet but will be available in the future. Each Future has a poll method that checks whether the task is complete. If the task is still running, it returns Pending; if it is complete, it returns Ready(value).
By itself, a Future does not perform any work. It only describes what should happen. To execute asynchronous code, you need a runtime — an executor that polls Futures and manages their execution.
The async and await Keywords
The async keyword transforms a regular function into an asynchronous one that returns a Future. For example:
async fn hello() -> String { "Hello, world!".to_string()}To get the result from a Future, you use .await. It suspends the execution of the current function until the Future is complete:
let result = hello().await;println!("{}", result);Important: .await can only be used inside an asynchronous function or an async {} block.
Lifecycle of an Asynchronous Task
- Creation of a
Future(calling an async function). - Passing the
Futureto the runtime for execution. - The runtime polls the
Futureviapoll. - If the task blocks (waiting for I/O), the runtime switches to another task.
- When the result is ready, the
FuturereturnsReady.
The Ecosystem: Tokio and async-std
To work with asynchronous code in Rust, asynchronous runtimes are used. The two most popular ones are Tokio and async-std.
Tokio
Tokio is the industry standard for asynchronous programming in Rust. It provides a multi-threaded scheduler, asynchronous I/O, timers, synchronization, and network protocols. Most major projects (for example, Hyper, Tonic, Axum) are built on Tokio.
To get started with Tokio, add the dependency to Cargo.toml:
[dependencies]tokio = { version = "1", features = ["full"] }The main macro for running asynchronous code is #[tokio::main]:
#[tokio::main]async fn main() { println!("Hello from Tokio!");}async-std
async-std is an alternative runtime that strives to be as similar as possible to the Rust standard library. It is simpler for beginners but less popular in production.
Example