ПрограммированиеСистемный программист

Как устроены асинхронность и работа с Future в Rust? В чем уникальные особенности реализации async/await и чем это отличается от подобных механизмов в других языках?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Асинхронность в Rust реализуется через Future — объект, представляющий работу, результат которой будет получен в будущем. Функции, объявленные с помощью async fn, возвращают анонимную структуру-генератор, реализующую трейт Future. Для запуска асинхронного кода используется рантайм (например, Tokio или async-std), поскольку стандартная библиотека не содержит встроенного event loop.

Главные особенности Rust:

  • Zero-cost abstractions — компилятор превращает async-код в state machine без аллокаций в куче, если позволяет задача.
  • Отсутствие нативного garbage collector — все ресурсы управляются явно, что требует особого внимания к lifetime и владению.
  • Send/Sync — ограничения на переносимость и синхронизацию задач между потоками.

Пример:

use tokio::time::sleep; use std::time::Duration; async fn foo() { println!("Hello"); sleep(Duration::from_secs(1)).await; println!("World!"); } #[tokio::main] async fn main() { foo().await; }

Вопрос с подвохом

Может ли асинхронная функция в Rust по умолчанию быть выполнена сразу при вызове? Почему?

Ответ: Нет. Вызов async fn возвращает не результат, а объект типа Future (state machine). Для реального выполнения требуется вызвать .await или передать Future рантайму. Сам вызов только создает описание задачи, но не выполняет её.

Пример:

async fn answer() -> u32 { 42 } let fut = answer(); // тут нет выполнения, только создание future let result = fut.await; // выполнение начинается здесь

Примеры реальных ошибок из-за незнания тонкостей темы


История

В highload backend проекте junior-разработчик объявил несколько async-функций, но нигде не вызвал .await. Из-за этого основные потоки выполнялись синхронно, что привело к просадке по производительности и росту времени отклика в 3 раза.

История

В микросервисе использовали async api, взаимодействуя через tokio. При миграции разработчик попытался использовать async-std и tokio одновременно, забыв, что event loop должен быть только один. В результате зависания и runtime panic, потому что оба рантайма конфликтовали.

История

Один из членов команды забыл про Send/Sync ограничения на типы, использующиеся внутри Future. При попытке поделиться future между потоками приложение упало с ошибкой компиляции, требующей реализации Send для определённой структуры, что потребовало пересмотра архитектуры хранения состояния.