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

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

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

Начать курс

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

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

Что такое асинхронное программирование и зачем оно нужно?

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

  • Сетевых приложений — веб-серверы, клиенты API, чаты.
  • Работы с файлами — чтение/запись больших объемов данных.
  • Пользовательских интерфейсов — чтобы интерфейс не "зависал" во время фоновых операций.

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

Основы async/await в Rust

Ключевые слова async и .await — это основа асинхронного программирования в Rust. Давайте разберем их на простом примере.

1. Создание асинхронной функции

Любая функция может стать асинхронной, если добавить перед ней ключевое слово async. Такая функция возвращает Future — значение, которое еще не готово, но будет готово в будущем.

// Синхронная функция
fn say_hello_sync() -> String {
    String::from("Hello, World!")
}

// Асинхронная функция
async fn say_hello_async() -> String {
    String::from("Hello, World!")
}

Обратите внимание: если вы просто вызовете say_hello_async(), код не выполнится. Вы получите объект Future, который нужно "запустить" с помощью runtime.

2. Ожидание результата с .await

Чтобы получить значение из Future, используйте .await. Это ключевое слово говорит компилятору: "приостанови выполнение этой функции, пока Future не будет готов, и дай другим задачам поработать".

async fn process() {
    let result = say_hello_async().await;
    println!("{}", result);
}

3. Асинхронные блоки

Вы также можете создавать асинхронные блоки кода "на лету":

let future = async {
    let x = 10;
    let y = 20;
    x + y
};

let result = future.await; // результат: 30

Runtime: Tokio — сердце асинхронного Rust

Сами по себе async и .await не запускают код. Нужен runtime — среда выполнения, которая управляет задачами и планировщиком. Самая популярная библиотека для этого — Tokio.

Установка Tokio

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

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

Первый асинхронный проект

Чтобы запустить асинхронный код, используйте макрос #[tokio::main]. Он автоматически создает runtime и запускает вашу функцию.

use tokio;

#[tokio::main]
async fn main() {
    println!("Запускаем асинхронный код!");
    let result = say_hello_async().await;
    println!("Результат: {}", result);
}

async fn say_hello_async() -> String {
    // Имитация долгой операции
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
    String::from("Hello, Async World!")
}

В этом примере tokio::time::sleep — асинхронная функция, которая приостанавливает выполнение на 2 секунды, но не блокирует весь поток. Пока одна задача "спит", runtime может выполнять другие задачи.

Запуск нескольких задач

Одно из главных преимуществ асинхронности — параллельное выполнение задач. Используйте tokio::spawn для запуска фоновых задач:

#[tokio::main]
async fn main() {
    let handle1 = tokio::spawn(async {
        println!("Задача 1 началась");
        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
        println!("Задача 1 завершена");
    });

    let handle2 = tokio::spawn(async {
        println!("Задача 2 началась");
        tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
        println!("Задача 2 завершена");
    });

    // Ждем завершения обеих задач
    handle1.await.unwrap();
    handle2.await.unwrap();
    println("Все задачи выполнены!");
}

Вывод будет:

Задача 1 началась
Задача 2 началась
Задача 1 завершена
Задача 2 завершена
Все задачи выполнены!

Обратите внимание: задачи выполняются одновременно, а не последовательно.

Практический пример: асинхронный HTTP-запрос

Давайте создадим программу, которая загружает данные с нескольких URL одновременно. Для этого используем крейт reqwest (асинхронный HTTP-клиент).

Установка зависимостей

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

Код программы

use reqwest;
use tokio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let urls = vec![
        "https://api.github.com/users/rust-lang",
        "https://api.github.com/users/tokio-rs",
        "https://api.github.com/users/serde-rs",
    ];

    let mut handles = vec![];

    for url in urls {                

Блоги

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