RustMiddleExperience

Для каких задач Rust является сильным выбором, а где лучше честно выбрать другой язык?

Rust оправдан для системного ПО, высоконагруженных сервисов и WebAssembly, где важны предсказуемая latency и memory safety. Для CRUD, прототипов и команд без опыта Rust лучше выбрать Go или Python.

Когда Rust — правильный выбор

Rust оправдывает затраты на крутую кривую обучения в нескольких конкретных сценариях:

  • Системное программирование и embedded — драйверы, прошивки, операционные системы. Нет рантайма и GC: no_std-крейты работают на микроконтроллерах с 64 КБ RAM.
  • Высоконагруженные сетевые сервисы — HTTP-прокси, DNS-резолверы, брокеры сообщений. Tokio + Hyper дают latency p99 < 1 мс без stop-the-world пауз.
  • WebAssembly — Rust компилируется в WASM лучше, чем большинство языков: размер бинаря минимальный, wasm-bindgen делает интеграцию с JS прозрачной.
  • Безопасность-критичные парсеры и криптография — borrow checker исключает use-after-free и data races на этапе компиляции, что критично для кода, обрабатывающего недоверенный ввод.
  • CLI-инструменты — быстрый старт, один статический бинарь, кросс-компиляция через cross.

Когда лучше выбрать другой язык

  • CRUD-приложения и внутренние сервисы — Go или Python/FastAPI дадут тот же throughput при в 3–5 раз меньших затратах на разработку. Borrow checker замедляет итерации.
  • Маленькая команда без опыта Rust — onboarding занимает 2–6 месяцев до продуктивности. Это реальная стоимость.
  • Data science и ML-пайплайны — экосистема уступает Python: нет аналога pandas/sklearn/torch с таким же community.
  • Прототипирование — borrow checker мешает быстро менять архитектуру. Python или Go позволяют проверить гипотезу за день.
  • Легаси-интеграции — если большая часть кода уже на JVM или .NET, добавление Rust создаёт FFI-мост с собственной сложностью.

Практический пример выбора

// Rust оправдан: парсер сетевых пакетов с нулевым копированием
use nom::{bytes::complete::take, number::complete::be_u16, IResult};

#[derive(Debug)]
struct EthernetFrame<'a> {
    dst_mac: &'a [u8],
    src_mac: &'a [u8],
    ethertype: u16,
    payload: &'a [u8],
}

fn parse_frame(input: &[u8]) -> IResult<&[u8], EthernetFrame> {
    let (input, dst_mac) = take(6usize)(input)?;
    let (input, src_mac) = take(6usize)(input)?;
    let (input, ethertype) = be_u16(input)?;
    Ok((&[][..], EthernetFrame { dst_mac, src_mac, ethertype, payload: input }))
}
// Lifetime 'a гарантирует: payload живёт не дольше входного буфера
// Никакого копирования — zero-copy parsing прямо из сетевого буфера

Подводные камни

  • Недооценка времени на обучение: «пишем на Rust» без опытного ревьюера в команде ведёт к клонированию всего подряд вместо понимания borrow checker.
  • Async Rust сложнее синхронного в 3–4 раза: futures, pinning, Send-bounds — это отдельная область знаний поверх базового Rust.
  • Ecosystem maturity неравномерна: для HTTP всё отлично (axum, actix), для GUI или ORM — значительно слабее чем в Go или Java.
  • Compile time: большой проект на Rust компилируется 2–10 минут даже с incremental builds. Это реально замедляет CI.
  • Cargo.lock не решает проблему supply chain: транзитивные зависимости могут тянуть небезопасный unsafe-код из чужих крейтов.
  • unsafe блоки снимают гарантии компилятора — их наличие в кодовой базе требует дополнительного аудита, как и в C.
  • Нет стабильного ABI: Rust-библиотеки нельзя просто так шарить между крейтами разных версий компилятора без extern "C".
  • Для команды с Go-бэкграундом переключение на Rust не всегда даёт выигрыш: Go тоже очень быстрый, а код проще поддерживать.

What hurts your answer

  • Выбирать Rust по популярности, а не по требованиям проекта
  • Игнорировать опыт команды, эксплуатацию и стоимость поддержки
  • Не называть ситуации, где Rust будет плохим выбором

What they're listening for

  • Называет критерии выбора Rust
  • Учитывает команду, эксплуатацию, стоимость и риски
  • Может назвать сценарии, где выбрал бы альтернативу

Related topics