На главную > Блог > Категория > 🦀 Асинхронность в Rust: что такое async / await и как это работает
Вы когда-нибудь задумывались, почему ваш торговый бот на Python может «подвиснуть» во время ожидания ответа от биржи? Или почему он обрабатывает WebSocket-события по одному, хотя данные приходят пачками? Это проблема синхронного кода: программа блокируется на каждой операции ввода-вывода (сеть, диск, таймер).
Rust предлагает элегантное решение — асинхронное программирование с ключевыми словами async и await. В этой статье я объясню, что такое асинхронность, зачем она нужна в трейдинге, и как использовать async/await в Rust без страха и боли.
«Асинхронный код — это как официант в ресторане. Пока он ждёт, пока повар готовит блюдо, он уже обслуживает другой столик. Синхронный код — это как единственный официант, который стоит и смотрит, как готовят, ничего не делая».
fn main() {
let price = get_price_from_binance(); // блокируем поток на 200 мс
let rsi = calculate_rsi(price); // блокируем ещё на 50 мс
send_order(rsi); // блокируем на 100 мс
}
Программа простаивает 350 мс, за которые можно было бы обработать ещё 10 тиков с биржи. В трейдинге это критично — вы теряете преимущество.
async fn main() {
let price = get_price_from_binance().await; // не блокирует поток
let rsi = calculate_rsi(price).await; // не блокирует
send_order(rsi).await; // не блокирует
}
Пока одна задача ждёт ответа от сети, рантайм переключается на другую задачу. Поток не простаивает.
В Rust асинхронные функции возвращают тип Future — это ленивое вычисление, которое ещё не запущено. Future можно выполнить, только дождавшись его (await) или передав в executor.
use std::future::Future;
// Асинхронная функция (возвращает Future)
async fn fetch_price() -> f64 {
// имитация запроса к бирже
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
67000.0
}
fn main() {
let future = fetch_price(); // Future не выполнен, просто объект
// Чтобы выполнить, нужен executor
}
Будущее (Future) в Rust ленивое — оно ничего не делает, пока его не опросят. Это отличает Rust от JavaScript, где async функции стартуют сразу.
Чтобы выполнить Future, нужен асинхронный рантайм. Самый популярный в Rust — Tokio. Он управляет очередью задач, потоками и таймерами.
#[tokio::main]
async fn main() {
let price = fetch_price().await;
println!("Цена: {}", price);
}
Макрос #[tokio::main] создаёт асинхронный рантайм и запускает в нём вашу main функцию.
Напишем простой трейдерский бот, который подключается к WebSocket Binance и обрабатывает тики асинхронно.
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.20"
futures-util = "0.3"
serde_json = "1"
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
use futures_util::{SinkExt, StreamExt};
use serde_json::Value;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Подключаемся к WebSocket Binance
let url = "wss://stream.binance.com:9443/ws/btcusdt@trade";
let (ws_stream, _) = connect_async(url).await?;
let (mut write, mut read) = ws_stream.split();
println!("✅ WebSocket подключён");
// Обрабатываем входящие сообщения
while let Some(msg) = read.next().await {
let msg = msg?;
if msg.is_text() {
let text = msg.to_text()?;
let json: Value = serde_json::from_str(text)?;
if let (Some(price), Some(volume)) = (json.get("p"), json.get("q")) {
let price_f64 = price.as_str().unwrap().parse::()?;
let volume_f64 = volume.as_str().unwrap().parse::()?;
println!("💰 BTC/USDT: {:.2}, объем: {:.2}", price_f64, volume_f64);
// Здесь можно вызвать стратегию
process_trade(price_f64, volume_f64).await;
}
}
}
Ok(())
}
async fn process_trade(price: f64, volume: f64) {
// Симуляция расчёта индикатора (не блокирует, т.к. async)
tokio::time::sleep(tokio::time::Duration::from_micros(10)).await;
if price > 68000.0 && volume > 10.0 {
println!("📈 Сигнал на покупку!");
// Отправка ордера тоже асинхронная
send_order("BUY", 0.01).await;
}
}
async fn send_order(side: &str, quantity: f64) {
// Имитация запроса к API биржи
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
println!("✅ Ордер {} {} отправлен", side, quantity);
}
В асинхронном коде ошибки нужно обрабатывать так же, как в синхронном — с помощью Result и ?.
use tokio::time::timeout;
use std::time::Duration;
async fn fetch_price_with_timeout() -> Result<f64, Box<dyn std::error::Error>> {
let result = timeout(Duration::from_secs(2), fetch_price()).await;
match result {
Ok(price) => Ok(price),
Err(_) => {
eprintln!("❌ Тайм-аут: биржа не ответила за 2 секунды");
Ok(0.0)
}
}
}
Асинхронность позволяет одновременно следить за BTC, ETH и SOL. Для этого используется tokio::join!.
async fn watch_ticker(symbol: &str) {
let url = format!("wss://stream.binance.com:9443/ws/{}@trade", symbol.to_lowercase());
// ... логика подключения
loop {
// обрабатываем тики
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
#[tokio::main]
async fn main() {
let btc = tokio::spawn(watch_ticker("BTCUSDT"));
let eth = tokio::spawn(watch_ticker("ETHUSDT"));
let sol = tokio::spawn(watch_ticker("SOLUSDT"));
// Ждём выполнения всех задач (параллельно)
let _ = tokio::join!(btc, eth, sol);
}
Только .await даёт рантайму переключиться на другую задачу. Если внутри асинхронной функции вызвать синхронную блокирующую операцию (например, std::thread::sleep), заблокируется весь поток рантайма. Используйте tokio::time::sleep.
// Плохо (блокирует поток)
std::thread::sleep(Duration::from_secs(1));
// Хорошо (не блокирует)
tokio::time::sleep(Duration::from_secs(1)).await;
Если вы запускаете асинхронные задачи на многопоточном рантайме, все данные, передаваемые между задачами, должны реализовывать трейт Send. Rc нельзя, используйте Arc.
Асинхронные функции не могут захватывать ссылки через await — используйте Arc или клонируйте данные.
Асинхронное программирование с async/await в Rust позволяет создавать высокопроизводительные, отзывчивые и безопасные торговые системы. Вы можете одновременно следить за сотней инструментов, обрабатывать WebSocket-события и отправлять ордера, не блокируя потоки.
Tokio предоставляет всё необходимое: таймеры, сети, файловый ввод-вывод, мьютексы и каналы для асинхронного кода. А строгая система типов Rust не даст вам сделать распространённые ошибки (например, забыть await).
Начните с малого: подключитесь к WebSocket Binance, обрабатывайте тики, отправляйте их в канал. Затем добавьте стратегию на основе скользящих средних. Потом — отправку ордеров. За выходные вы сможете написать бота, который работает быстрее, чем его аналог на Python, и не «зависает» при просадке сети.
И помните: асинхронность — это не магия, а инструмент. Он требует понимания, но открывает возможности, недоступные синхронному коду. В мире HFT и real-тайм трейдинга асинхронность — это не роскошь, а необходимость.
«Асинхронный Rust — как швейцарские часы. Каждая деталь на своём месте, и весь механизм работает без задержек. А Tokio — это мастер, который собирает эти часы».
Дата размещения статьи: 09-06-2026 в 09:08:16