Крейт async_compat
Источник: Документация async_compat
Адаптер совместимости между tokio и futures.
Описание
Существует два вида проблем совместимости между tokio и futures:
-
Типы Tokio не могут использоваться вне контекста tokio, поэтому любая попытка их использования приведет к панике.
Решение: Если вы примените адаптер
Compatк фьючерсу, фьючерс вручную войдет в контекст глобального runtime tokio. Если runtime уже доступен через thread-local переменные tokio, он будет использован. В противном случае, новый однопоточный runtime будет создан по требованию. Это не означает, что фьючерс опрашивается runtime tokio - это означает, что фьючерс устанавливает thread-local переменную, указывающую на глобальный runtime tokio, чтобы типы tokio могли использоваться внутри него. -
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; })); }