Асинхронное программирование в Rust: полное руководство для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в асинхронное программирование в Rust

Асинхронное программирование — это подход, который позволяет вашему приложению эффективно выполнять несколько задач одновременно, не блокируя основной поток выполнения. В Rust этот подход реализован через модель async/await, которая обеспечивает безопасность памяти и высокую производительность, свойственные языку.

В отличие от традиционной многопоточности, асинхронный код работает в рамках одного или нескольких потоков, переключаясь между задачами во время ожидания (например, ввода-вывода или сетевых запросов). Это делает его идеальным для создания высоконагруженных веб-серверов, сетевых приложений и инструментов обработки данных.

В этой статье мы разберём основы асинхронного программирования в Rust: от ключевых концепций до практических примеров. Вы узнаете, как работают Future, async и await, познакомитесь с популярным рантаймом Tokio и напишете свой первый асинхронный проект.

Основные концепции: Future, async и await

Что такое Future?

В Rust асинхронная задача представлена трейтом Future. Future — это значение, которое может быть ещё не готово, но будет получено в будущем. Каждый Future имеет метод poll, который проверяет, завершена ли задача. Если задача ещё выполняется, возвращается Pending; если завершена — Ready(value).

Сам по себе Future не выполняет никакой работы. Он лишь описывает, что должно произойти. Для выполнения асинхронного кода требуется рантайм — исполнитель, который опрашивает Future и управляет их выполнением.

Ключевые слова async и await

Ключевое слово async преобразует обычную функцию в асинхронную, которая возвращает Future. Например:

async fn hello() -> String {
    "Привет, мир!".to_string()
}

Чтобы получить результат из Future, используется .await. Он приостанавливает выполнение текущей функции до тех пор, пока Future не будет завершён:

let result = hello().await;
println!("{}", result);

Важно: .await можно использовать только внутри асинхронной функции или блока async {}.

Жизненный цикл асинхронной задачи

  • Создание Future (вызов асинхронной функции).
  • Передача Future рантайму для выполнения.
  • Рантайм опрашивает Future через poll.
  • Если задача блокируется (ожидание I/O), рантайм переключается на другую задачу.
  • Когда результат готов, Future возвращает Ready.

Экосистема: Tokio и async-std

Для работы с асинхронным кодом в Rust используются асинхронные рантаймы. Два самых популярных — Tokio и async-std.

Tokio

Tokio — это промышленный стандарт для асинхронного программирования в Rust. Он предоставляет многопоточный планировщик, асинхронный ввод-вывод, таймеры, синхронизацию и сетевые протоколы. Большинство крупных проектов (например, Hyper, Tonic, Axum) построены на Tokio.

Чтобы начать работу с Tokio, добавьте зависимость в Cargo.toml:

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

Основной макрос для запуска асинхронного кода — #[tokio::main]:

#[tokio::main]
async fn main() {
    println!("Hello from Tokio!");
}

async-std

async-std — альтернативный рантайм, который стремится быть максимально похожим на стандартную библиотеку Rust. Он проще для начинающих, но менее популярен в продакшене.

Пример с async-std:

use async_std::task;

async fn say_hello() {
    println!("Hello!");
}

fn main() {
    task::block_on(say_hello());
}

Для этого руководства мы будем использовать Tokio, так как он является де-факто стандартом в индустрии.

Практический пример: асинхронный веб-скрапинг

Давайте создадим простую программу, которая асинхронно загружает содержимое нескольких веб-страниц и выводит их заголовки. Это классический пример, демонстрирующий преимущества асинхронности.

Шаг 1: Настройка проекта

Создайте новый проект:

cargo new async_scraper
cd async_scraper

Добавьте в Cargo.toml:

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

Шаг 2: Написание кода

Создадим асинхронную функцию для загрузки одной страницы:

use reqwest;
use tokio;

async fn fetch_title(url: &str) -> Result<String, reqwest::Error> {
    let response = reqwest::get(url).await?;
    let body = response.text().await?;
    
    // Простейший парсинг: ищем текст между <title> и </title>
    let start = body.find("<title>").map(|i| i + 7);
    let end = start.and_then(|s| body[s..].find("</title>").map(|e| s + e));
    
    let title = match (start, end) {
        (Some(s), Some(e)) => body[s..e].to_string(),
        _ => "Заголовок не найден".to_string(),
    };
    
    Ok(title)
}

Теперь напишем основную функцию, которая запускает несколько задач параллельно:

#[tokio::main]
async fn main() {
    let urls = vec![
        "https://www.rust-lang.org",
        "https://tokio.rs",
        "https://crates.io",
    ];
    
    let mut tasks = Vec::new();
    
    for url in urls {
        tasks.push(tokio::spawn(async move {
            match fetch_title(url).                

Блоги

Рекомендаций по книгам