Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Крейт async_compat

Источник: Документация async_compat

Адаптер совместимости между tokio и futures.

Описание

Существует два вида проблем совместимости между tokio и futures:

  1. Типы Tokio не могут использоваться вне контекста tokio, поэтому любая попытка их использования приведет к панике.

    Решение: Если вы примените адаптер Compat к фьючерсу, фьючерс вручную войдет в контекст глобального runtime tokio. Если runtime уже доступен через thread-local переменные tokio, он будет использован. В противном случае, новый однопоточный runtime будет создан по требованию. Это не означает, что фьючерс опрашивается runtime tokio - это означает, что фьючерс устанавливает thread-local переменную, указывающую на глобальный runtime tokio, чтобы типы tokio могли использоваться внутри него.

  2. Tokio и futures имеют похожие, но разные I/O трейты AsyncRead, AsyncWrite, AsyncBufRead и AsyncSeek.

    Решение: Когда адаптер Compat применяется к I/O типу, он будет реализовывать трейты противоположного вида. Таким образом вы можете использовать типы на основе tokio там, где ожидаются типы на основе futures, и наоборот.

Вы можете применить адаптер Compat с помощью конструктора Compat::new() или используя любой метод из трейта CompatExt.

Примеры

Эта программа читает строки из stdin и выводит их в stdout, но она не будет работать:

fn main() -> std::io::Result<()> {
    futures::executor::block_on(async {
        let stdin = tokio::io::stdin();
        let mut stdout = tokio::io::stdout();

        // Следующая строка не будет работать по двум причинам:
        // 1. Ошибка времени выполнения, потому что stdin и stdout используются вне контекста tokio.
        // 2. Ошибка компиляции из-за несовпадения трейтов `AsyncRead` и `AsyncWrite`.
        futures::io::copy(stdin, &mut stdout).await?;
        Ok(())
    })
}

Чтобы обойти проблемы совместимости, примените адаптер Compat к stdin, stdout и futures::io::copy():

use async_compat::CompatExt;

fn main() -> std::io::Result<()> {
    futures::executor::block_on(async {
        let stdin = tokio::io::stdin();
        let mut stdout = tokio::io::stdout();

        futures::io::copy(stdin.compat(), &mut stdout.compat_mut()).compat().await?;
        Ok(())
    })
}

Также возможно применить Compat к внешнему фьючерсу, передаваемому в futures::executor::block_on(), а не к самому futures::io::copy(). Когда адаптер применяется к внешнему фьючерсу, отдельные внутренние фьючерсы не нуждаются в адаптере, потому что они теперь все внутри контекста tokio:

use async_compat::{Compat, CompatExt};

fn main() -> std::io::Result<()> {
    futures::executor::block_on(Compat::new(async {
        let stdin = tokio::io::stdin();
        let mut stdout = tokio::io::stdout();

        futures::io::copy(stdin.compat(), &mut stdout.compat_mut()).await?;
        Ok(())
    }))
}

Адаптер совместимости преобразует между I/O типами на основе tokio и futures в любом направлении. Вот как мы можем написать ту же программу, используя I/O типы на основе futures внутри tokio:

use async_compat::CompatExt;
use blocking::Unblock;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut stdin = Unblock::new(std::io::stdin());
    let mut stdout = Unblock::new(std::io::stdout());

    tokio::io::copy(&mut stdin.compat_mut(), &mut stdout.compat_mut()).await?;
    Ok(())
}

Наконец, мы можем использовать любой крейт на основе tokio из любого другого асинхронного runtime. Вот пример с reqwest и warp:

use async_compat::{Compat, CompatExt};
use warp::Filter;

fn main() {
    futures::executor::block_on(Compat::new(async {
        // Выполняем HTTP GET запрос.
        let response = reqwest::get("https://www.rust-lang.org").await.unwrap();
        println!("{}", response.text().await.unwrap());

        // Запускаем HTTP сервер.
        let routes = warp::any().map(|| "Hello from warp!");
        warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
    }))
}

Структуры

СтруктураОписание
CompatАдаптер совместимости для фьючерсов и I/O типов

Трейты

ТрейтОписание
CompatExtПрименяет адаптер Compat к фьючерсам и I/O типам

Ключевые особенности

Решение проблем контекста

  • Автоматическое создание runtime: Создает runtime tokio при необходимости
  • Thread-local контекст: Устанавливает контекст для использования типов tokio
  • Прозрачность: Минимальные изменения в существующем коде

Решение проблем I/O трейтов

  • Двунаправленное преобразование: Совместимость в обоих направлениях
  • Трейты futures ↔ tokio: Автоматическая реализация соответствующих трейтов
  • Универсальность: Работает с любыми I/O типами

Методы трейта CompatExt

МетодНазначение
compat()Применяет адаптер к значению
compat_mut()Применяет адаптер к изменяемой ссылке
compat_ref()Применяет адаптер к неизменяемой ссылке

Сценарии использования

Использование tokio кода с futures исполнителем

#![allow(unused)]
fn main() {
use async_compat::CompatExt;

futures::executor::block_on(async {
    let client = reqwest::Client::new();
    let response = client.get("http://example.com").send().compat().await?;
    // ...
});
}

Смешивание I/O типов

#![allow(unused)]
fn main() {
use async_compat::CompatExt;

// Использование tokio TcpStream с futures кодом
let stream = tokio::net::TcpStream::connect("127.0.0.1:8080").await?;
futures::io::copy(&mut stream.compat(), &mut output).await?;
}

Интеграция библиотек

#![allow(unused)]
fn main() {
use async_compat::{Compat, CompatExt};

// Использование warp (tokio) с futures исполнителем
futures::executor::block_on(Compat::new(async {
    warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
}));
}