Asynchronous Programming in Rust: A Complete Guide with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to Asynchronous Programming in Rust



Asynchronous programming is a paradigm that allows performing I/O operations (network requests, file reading, database operations) without blocking the main execution thread. In Rust, asynchrony is implemented through the Future system and the async/await keywords. Unlike multithreading, asynchronous code runs in a single thread, switching between tasks while waiting.



In this guide, we will cover:

  • Basics of async/await in Rust
  • Working with the tokio crate — the most popular async runtime
  • Creating and combining Future
  • Practical examples: async HTTP client and timers


This article is aimed at developers familiar with basic Rust syntax. If you are just starting out, we recommend first studying the basics of the language on PythonLib.



Basics of async/await and Future in Rust



What is a Future?



Future is a trait representing an asynchronous operation that may complete in the future. In Rust, Future is lazy: it does nothing until it is polled. The async runtime (e.g., Tokio) is responsible for polling.



A simple example of an async function:



use std::time::Duration;

async fn say_hello() { println!("Hello from the async world!"); tokio::time::sleep(Duration::from_secs(1)).await; println!("1 second has passed");}

#[tokio::main]async fn main() { say_hello().await;}


The async keyword turns a function into a Future. Calling .await suspends the execution of the current task until the Future completes, without blocking the thread.



Lifecycle of an async task



When you call .await, the following happens:

  1. The runtime polls the Future (calls the poll() method)
  2. If the Future is ready — the result is returned
  3. If the Future is waiting (e.g., a network operation) — the runtime switches to another task
  4. When the resource becomes available, the runtime resumes the task


An important feature of Rust: .await can only be called inside an async function or an async {} block. Attempting to use it in a synchronous context will result in a compilation error.



Practical Work with Tokio



Installation and Setup



Tokio is an async runtime for Rust, providing a task scheduler, timers, network sockets, and much more. To get started, add to Cargo.toml:



[dependencies]tokio = { version = "1", features = ["full"] }


The features = ["full"] flag enables all Tokio features. For production, it is better to select only the necessary features.



Running Multiple Async Tasks



One of the main advantages of asynchrony is the parallel execution of many tasks. Use tokio::spawn to run tasks in the background:



use tokio::time::{sleep, Duration};

async fn task(name: &str, seconds: u64) { println!("Task {} started", name); sleep(Duration::from_secs(seconds)).await; println!("Task {} completed after {} sec", name, seconds);}

#[tokio::main]async fn main() { let handle1 = tokio::spawn(task("A", 2)); let handle2 = tokio::spawn(task("B", 1));

// Wait for both tasks to complete let _ = tokio::join!(handle1, handle2); println!("All tasks completed");}


The tokio::join! macro waits for all passed Futures to complete. If one task finishes earlier, execution does not interrupt — we wait for the others.



Error Handling in Async Code

Blogs

Book Recommendations