Экосистема и ресурсы: веб, встраиваемое, WASM, куда двигаться дальше
Двенадцать предыдущих статей отвечали на вопрос «как устроен язык». Эта отвечает на вопрос «что я теперь могу на нём построить и чем за это заплачу». Разница принципиальная: язык вы уже знаете, а вот где именно Rust — рациональный выбор, а где дорогая ошибка, из синтаксиса не выводится. Это свойство экосистемы: какие крейты зрелые, какие домены закрыты, сколько стоит найм, сколько идёт сборка, кто починит библиотеку, если её автор пропадёт.
Здесь важно честно признать одну вещь. Стандартная библиотека Rust намеренно маленькая: в ней нет HTTP-клиента, нет асинхронного рантайма, нет генератора случайных чисел, нет сериализации, нет регулярных выражений, нет TLS. В Go, C# и Java половина этого лежит «в коробке» и сопровождается вендором. В Rust всё это — сторонние крейты, и решение «какой взять» вы принимаете сами, в первый же день проекта. Именно поэтому статья про экосистему в треке Rust — не приложение к курсу, а обязательная его часть: выбор зависимости в Rust имеет больший вес, чем в большинстве других языков.
Заодно закроем то, о чём принято молчать: скорость компиляции, кривая обучения, стоимость команды и список доменов, куда Rust тащить не надо.
Как устроена экосистема: одна регистратура, тонкая стандартная библиотека
Технически всё просто: единственный публичный реестр crates.io, единый клиент cargo, документация автоматически собирается на docs.rs для каждой опубликованной версии. Счёт крейтов идёт на сотни тысяч, но полезная часть куда меньше, и ориентироваться в ней помогают кураторские списки — blessed.rs и lib.rs (последний — альтернативный фронтенд к тому же реестру, с более честной статистикой по сопровождению).
Устройство реестра порождает три следствия, которые лучше знать заранее.
Имена глобальные и не возвращаются. Пространств имён вроде @scope/package в crates.io нет: имя http занято раз и навсегда. Отсюда тайпсквоттинг и путаница «а rand_core — это от тех же людей, что rand?». Проверять принадлежность приходится по репозиторию, а не по имени.
Версия 0.x — это мажорная версия. Cargo трактует 0.4.1 и 0.5.0 как несовместимые. Практический эффект: reqwest 0.11 и reqwest 0.12 спокойно живут в одном дереве зависимостей, каждая тянет свою версию hyper, и вы получаете два HTTP-стека в бинаре и загадочную ошибку «expected Body, found Body» — типы из разных версий одного крейта разные. Лечится cargo tree --duplicates.
Фичи (features) аддитивны и склеиваются по всему графу. Если ваш крейт просит tokio без rt-multi-thread, а зависимость просит с ним — в сборку попадёт объединение. Отсюда правило: default-features = false плюс явный список — не педантизм, а способ управлять и размером бинаря, и временем сборки.
Экосистема во времени: что уже устоялось, а что ещё нет
Ключевой вопрос при выборе технологии — не «есть ли крейт», а «устоялся ли он». В Rust полезно помнить хронологию: язык молодой, и часть слоёв застыла буквально недавно.
Читать эту шкалу надо так: слои, застывшие в 2019–2021 годах (веб-бэкенд, CLI, сериализация), сегодня скучно-надёжны. Слои 2024–2025 годов (async в трейтах, компонентная модель WASM, embedded-hal 1.0) уже пригодны, но экосистема вокруг них ещё догоняет: часть драйверов и библиотек висит на старых версиях. А GUI и мобильная разработка не застыли до сих пор.
Выбор крейта: пять вопросов до cargo add
Зависимость в Rust дорого стоит по двум статьям сразу: она попадает в ваше время сборки навсегда и, если у неё есть build.rs или процедурный макрос, выполняет произвольный код на вашей машине и в вашем CI в момент компиляции. Это не теоретический риск, а тот же класс угроз, что разбирается в статье «Цепочка поставок». Пять вопросов, которые дешевле задать сейчас:
- Кто сопровождает и как давно. Дата последнего релиза, число открытых issue без ответа, есть ли организация за репозиторием. Крейт из одного человека — нормально; крейт из одного человека в фундаменте платёжного сервиса — риск, который надо назвать вслух.
- Какой MSRV (minimum supported Rust version) и есть ли политика его смены. Если вы собираете в Debian stable или в закрытом контуре, крейт, поднимающий MSRV в патч-релизе, сломает вам сборку.
- Сколько
unsafeи есть ли аудит. Смотрите наcargo geiger, на наличие тестов под Miri, на упоминание аудита. Для криптографии и парсеров это первое, на что стоит смотреть. - Что он тянет за собой.
cargo tree -e normalдо добавления — привычка, экономящая минуты сборки. Крейт на 12 строк, тянущийsynиproc-macro2, стоит дороже, чем кажется. - Есть ли он в базе уязвимостей. RustSec Advisory Database плюс
cargo audit. Отдельная категория — крейты со статусом «unmaintained»: они не уязвимы, но чинить их некому.
Автоматизируется это одним файлом. cargo-deny проверяет лицензии, дубликаты версий, запрещённые крейты и уязвимости — и ставится в CI рядом с тестами:
# deny.toml — конфигурация cargo-deny, запускается как `cargo deny check`
[advisories]
# Уязвимость валит сборку, «заброшенный крейт» — предупреждение с явным списком исключений.
yanked = "deny"
ignore = ["RUSTSEC-2024-0000"] # осознанные исключения только с комментарием и сроком
[licenses]
allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC", "Unicode-3.0"] # только проверенное юристами
[bans]
multiple-versions = "warn" # две версии крейта в дереве — лишние минуты сборки и мегабайты
deny = [{ name = "openssl-sys" }] # в проекте только rustls, системный OpenSSL не тянем
[sources]
unknown-registry = "deny"
unknown-git = "deny"
Для более серьёзных требований есть cargo-vet от Mozilla: он ведёт журнал «эту версию этого крейта человек посмотрел глазами» и умеет импортировать чужие аудиты. Это единственный масштабируемый ответ на вопрос «мы правда прочитали 400 зависимостей?» — не прочитали, но знаем, кто прочитал.
Веб-бэкенд: самый скучный и потому самый готовый слой
Если вы пишете HTTP-сервис, экосистема Rust сегодня даёт полный комплект. Основной выбор — между axum (поверх tokio и tower, ведётся командой tokio) и actix-web (свой рантайм акторов внутри, исторически лидер синтетических бенчмарков). По умолчанию берите axum: у него меньше собственных абстракций, а tower::Service — общий интерфейс middleware, который переиспользуют десятки крейтов. Типовой сервис выглядит так:
use axum::{extract::{Path, State}, http::StatusCode, routing::get, Json, Router};
use serde::Serialize;
use sqlx::PgPool;
use std::time::Duration;
use tower_http::{timeout::TimeoutLayer, trace::TraceLayer};
#[derive(Serialize, sqlx::FromRow)]
struct Order { id: i64, total_cents: i64, status: String }
// Хендлер — обычная async-функция. axum разбирает аргументы по типам:
// State отдаёт общее состояние, Path вытаскивает параметр из маршрута.
async fn get_order(
State(pool): State<PgPool>,
Path(id): Path<i64>,
) -> Result<Json<Order>, StatusCode> {
let order = sqlx::query_as::<_, Order>("SELECT id, total_cents, status FROM orders WHERE id = $1")
.bind(id)
.fetch_optional(&pool)
.await
.map_err(|e| {
// Ошибку БД логируем целиком, наружу отдаём только код: детали клиенту не показываем.
tracing::error!(error = %e, "запрос к базе не удался");
StatusCode::INTERNAL_SERVER_ERROR
})?
.ok_or(StatusCode::NOT_FOUND)?;
Ok(Json(order))
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let pool = PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
let app = Router::new()
// Внимание: в axum 0.8 параметр пути пишется в фигурных скобках.
// В 0.7 и раньше было "/orders/:id" — это самая частая ошибка при обновлении.
.route("/orders/{id}", get(get_order))
.layer(TimeoutLayer::new(Duration::from_secs(3)))
.layer(TraceLayer::new_for_http())
.with_state(pool);
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
axum::serve(listener, app).await?;
Ok(())
}
Ключевая идея, ради которой стоит понять tower: middleware — это не «список хуков фреймворка», а обёртка одного Service вокруг другого. Слои композируются как функции, порядок — снаружи внутрь, и любой слой может завершить обработку, не вызывая внутренний сервис.
а future хендлера просто дропается TO-->>T: ответ, таймер снят T-->>C: 200 OK, в логах длительность и статус
Обратите внимание на строку с Note: отмена в Rust-асинхронности — это дроп future, а не сигнал внутрь задачи. Половина продовых сюрпризов на axum растёт отсюда, и разбирались они в статье «Асинхронный Rust».
Данные
Три подхода, и выбор между ними не эстетический:
- sqlx — обычный SQL плюс проверка запросов на этапе компиляции: макрос
query!ходит в живую базу и сверяет типы столбцов. Плата: для сборки в CI нужен либо доступ к БД, либо закоммиченный офлайн-кеш (cargo sqlx prepare, каталог.sqlx). Забыли обновить кеш после миграции — сборка падает, и это правильно. - diesel — типобезопасный DSL и синхронный API, самый строгий и самый «своя вселенная». Хорош там, где схема стабильна, а запросы сложные.
- SeaORM — привычная ORM поверх sqlx, ближе к тому, что ожидает человек из мира Django или EF Core.
Смежные вопросы (индексы, планы, изоляция) язык не решает: они разбираются в треке «Базы данных», в частности в статье «Индексы и планы запросов».
Грабли веб-стека: как читать «нереализованный трейт Handler»
Самое пугающее сообщение новичка в axum выглядит так:
error[E0277]: the trait bound `fn(Path<i64>, State<PgPool>) -> impl Future<Output = ...> {get_order}: Handler<_, _>` is not satisfied
--> src/main.rs:38:32
|
38 | .route("/orders/{id}", get(get_order))
| --- ^^^^^^^^^ the trait `Handler<_, _>` is not implemented
| |
| required by a bound introduced by this call
|
= note: Consider using `#[axum::debug_handler]` to improve the error message
Сообщение почти не помогает, потому что Handler реализован через набор blanket-impl для функций с подходящими аргументами: не подошёл один аргумент — «не реализован» весь трейт. Реальных причин ровно четыре, и они перечисляются быстрее, чем читается ошибка:
- Экстрактор, потребляющий тело запроса (
Json,String,Bytes,Form), стоит не последним. Тело можно прочитать один раз, поэтому такой экстрактор обязан быть последним аргументом. - Тип возврата не реализует
IntoResponse.Result<Json<T>, MyError>работает только если уMyErrorестьimpl IntoResponse. - Future хендлера не
Send. ДержитеRc,RefCellилиMutexGuardчерез.await— и всё разваливается ровно здесь, хотя причина в другой строке. - Тип состояния не совпадает с тем, что задан в
with_state.
Практический рецепт: повесить #[axum::debug_handler] на функцию. Макрос разворачивает проверку в понятное сообщение с указанием конкретного аргумента. Это общий приём для Rust: когда ошибка приходит от blanket-impl, ищите инструмент, который проверяет условия по одному.
Честно: где Rust на бэкенде выигрывает, а где нет
Выигрывает, когда важен хвост распределения задержек и предсказуемость: нет сборщика мусора — нет пауз, о которых рассказывает статья «Сборка мусора». Классический публичный кейс — переход Discord с Go на Rust: проблема была не в средней скорости, а в периодических всплесках задержки от GC. Выигрывает на прокси и сетевых узлах, где важен расход памяти на соединение — так появился Pingora от Cloudflare. Выигрывает на serverless из-за времени холодного старта и на CPU-bound обработке (парсинг, сжатие, криптография, конвертация форматов).
Проигрывает на обычном CRUD с быстро меняющимися требованиями. Каждое изменение схемы данных в Rust — это правка типов, а компилятор потребует обработать все случаи. Там, где в Python вы за час собираете эндпоинт и правите его пять раз в неделю, Rust будет стоить дороже — и это не недостаток языка, а несовпадение с задачей. Проигрывает там, где нужны богатые зрелые интеграции с корпоративными системами: SDK для «редкого» вендора чаще есть под Java и C#, чем под Rust. Проигрывает при найме: инженеров с продовым Rust-опытом на рынке в разы меньше.
WebAssembly: не одна технология, а две
Слово «WASM» в резюме скрывает два почти не пересекающихся мира. Они отличаются целью сборки, набором доступного API и типом решаемых задач.
Схема объясняет главное ограничение обоих миров: через границу проходят только числа. Строка передаётся как пара (указатель, длина), а сами байты хост читает прямо из линейной памяти модуля. Отсюда следуют и стоимость (копирование на каждом вызове), и безопасность (модуль не может дотянуться ни до чего, что ему не передали импортом).
Мир первый: браузер, цель wasm32-unknown-unknown
Задача — унести в браузер вычисление, которое на JS слишком медленное или которое уже написано на Rust: обработка изображений, разбор бинарных форматов, шифрование, физика, шахматный движок, редактор с CRDT. Инструменты: wasm-bindgen генерирует клей между Rust и JS, wasm-pack собирает npm-пакет, web-sys и js-sys дают типизированный доступ к браузерным API.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
// Импорт: функция хоста, которую модуль сможет вызвать.
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
/// Экспорт: из JS вызывается как обычная функция после await init().
#[wasm_bindgen]
pub fn normalize_csv(input: &str) -> String {
log(&format!("на входе {} байт", input.len()));
input.lines().map(|l| l.trim().to_lowercase()).collect::<Vec<_>>().join("\n")
}
[lib]
crate-type = ["cdylib", "rlib"] # без cdylib файл .wasm просто не появится
[profile.release]
opt-level = "z" # оптимизируем размер, а не скорость: в браузере важнее вес
lto = true # межмодульная оптимизация выкидывает неиспользуемое
codegen-units = 1
panic = "abort" # раскрутка стека в wasm стоит килобайтами кода
strip = true
Три грабли, на которые наступают все:
Размер. «Hello, world» на wasm-bindgen — это не 5 КБ, а десятки килобайт, и без настроек профиля легко получить пару сотен. Порядок действий: профиль как выше → wasm-opt -Oz из binaryen → анализ через twiggy, которая покажет, кто именно занял место. Чаще всего виноваты форматирование строк, паники с сообщениями и serde_json. Отдельно: wee_alloc больше не рекомендуется — крейт не сопровождается и имеет известные проблемы; берите системный аллокатор по умолчанию либо talc/lol_alloc.
Паника без текста. С panic = "abort" паника превращается в инструкцию unreachable, и в консоли вы увидите RuntimeError: unreachable executed без единого слова о причине. Лечится крейтом console_error_panic_hook, подключённым в debug-сборке.
Потоков нет по умолчанию. wasm-потоки требуют SharedArrayBuffer, а он требует заголовков кросс-изоляции Cross-Origin-Opener-Policy и Cross-Origin-Embedder-Policy на вашем сайте. Это инфраструктурное решение, а не флаг компилятора; wasm-bindgen-rayon помогает, но заголовки всё равно ставить вам.
Отдельная ветка — фронтенд-фреймворки целиком на Rust: Leptos, Dioxus, Yew. Они работают и умеют SSR с гидратацией, но платите вы размером бандла и незрелостью экосистемы компонентов. Здравая позиция: на Rust имеет смысл писать вычислительное ядро, а UI оставить тому, для чего браузер создавался — соображения из статьи «Веб-производительность» никуда не деваются, и лишние 300 КБ wasm легко съедят весь выигрыш.
Мир второй: WASI, цель wasm32-wasip1 и компонентная модель
Здесь wasm — не «ускоритель для браузера», а переносимая песочница на сервере. Модуль запускается в Wasmtime или WasmEdge, доступ к файлам, сети и часам получает через WASI, причём только к тому, что явно разрешил хост. Это делает wasm удобным форматом плагинов: пользовательский код исполняется внутри вашего процесса, но не может ни читать чужие файлы, ни ходить в сеть. Так работают фильтры Envoy (proxy-wasm), пользовательские трансформации в Vector, плагины Zellij и функции в Spin. Первое, обо что тут спотыкаются: цель надо доустановить, и она была переименована.
# Старое имя wasm32-wasi удалено в Rust 1.84; актуальные — wasm32-wasip1 и wasm32-wasip2.
rustup target add wasm32-wasip1
cargo build --release --target wasm32-wasip1
wasmtime run --dir=. target/wasm32-wasip1/release/tool.wasm
Без установленной цели компилятор скажет ровно то, что нужно сделать:
error[E0463]: can't find crate for `core`
|
= note: the `wasm32-wasip1` target may not be installed
= help: consider downloading the target with `rustup target add wasm32-wasip1`
Это, кстати, хорошая иллюстрация общего свойства: сообщения rustc почти всегда содержат следующий шаг. Привычка дочитывать до help: экономит часы.
Компонентная модель (wasm32-wasip2, wit-bindgen, cargo-component) — следующий слой: интерфейсы описываются на языке WIT, а не «числами в линейной памяти», и компоненты на разных языках соединяются без ручного клея. Технология уже пригодна, но экосистема догоняет; закладываться на неё стоит осознанно, а не «потому что будущее».
Встраиваемое: no_std — это отдельный диалект
Здесь Rust претендует на территорию C, и претендует всерьёз: те же байты, та же прошивка, но без переполнений буфера и с типами, которые не дают перепутать пины. Чтобы туда войти, надо сначала понять, из чего состоит стандартная библиотека.
Крейт с #![no_std] теряет std, но не теряет язык: трейты, дженерики, итераторы, проверка заимствований работают в прошивке так же, как на сервере. Отсюда лучшая практика для библиотек: писать на core, а alloc и std включать фичами — тогда один крейт едет и в прошивку, и в браузер, и на сервер.
Слоистость драйверов зафиксирована крейтом embedded-hal, версия 1.0 которого вышла в 2024 году и наконец дала стабильный контракт: драйвер датчика пишется один раз против трейтов и работает на любом чипе, у которого есть их реализация.
Ценность схемы — в стрелке от bme280 к трейтам: драйвер не знает про ваш чип. Это ровно та развязка, которую в C делают через указатели на функции и void*, только здесь она проверяется компилятором и не стоит ни байта на этапе выполнения — мономорфизация схлопывает всё в прямые вызовы.
Асинхронный код в прошивке — не роскошь: Embassy даёт исполнителя, который вместо ожидания в цикле усыпляет ядро до прерывания, что напрямую снижает потребление.
#![no_std] // std недоступен: нет ОС
#![no_main] // точку входа даёт cortex-m-rt, а не рантайм std
use embassy_executor::Spawner;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_time::{Duration, Timer};
// Крейты подключаются «ради побочного эффекта»: логгер и обработчик паники.
use {defmt_rtt as _, panic_probe as _};
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
let mut led = Output::new(p.PC13, Level::High, Speed::Low);
loop {
led.toggle();
// Здесь нет busy-wait: исполнитель уводит ядро в сон до срабатывания таймера,
// и ток потребления падает на порядок по сравнению с пустым циклом задержки.
Timer::after(Duration::from_millis(500)).await;
}
}
Две ошибки компилятора, которые встречает каждый в первый день, и что они значат на самом деле:
error[E0463]: can't find crate for `std`
|
= note: the `thumbv7em-none-eabihf` target does not support the standard library
= note: `std` is required by `firmware` because it does not declare `#![no_std]`
Значение: вы (или любая ваша зависимость) просите std на цели, где ОС нет. Ищите виновника через cargo tree и default-features = false — чаще всего это serde без no_std-фичи или крейт логирования.
error: `#[panic_handler]` function required, but not found
Значение: в std обработчик паники даёт рантайм; здесь его нет, и решение — ваше. panic-halt (зависнуть), panic-probe (сообщить отладчику), panic-reset (перезагрузиться) — это разные продуктовые решения, а не разные библиотеки. На реальном устройстве выбор между «зависнуть» и «перезагрузиться» обсуждается с теми, кто отвечает за безопасность изделия.
Рабочий инструментарий: probe-rs вместо связки OpenOCD и GDB (cargo embed, probe-rs run прошивает и выводит логи), defmt вместо форматирования строк на устройстве (форматирование делает хост, во флеш едут только идентификаторы), cargo size и cargo bloat для контроля образа. Для Cortex-M альтернатива Embassy — RTIC, который строит статическое расписание на прерываниях и доказывает отсутствие гонок на этапе компиляции. Espressif официально поддерживает Rust для ESP32 (esp-rs), для Raspberry Pi Pico есть embassy-rp.
Аппаратная база, прерывания, питание и протоколы — не тема языкового трека; они разбираются в треке «Встраиваемые системы», начиная с обзора, и особенно в статьях «Прерывания и тайминг» и «RTOS». Сравнение с тем, как то же самое делается на C, — в «C для встраиваемых».
Честная оговорка: во встраиваемом Rust вы регулярно упираетесь в отсутствие драйвера для конкретной микросхемы, в PAC, сгенерированный из кривого SVD, и в необходимость написать unsafe-обёртку самому. Механику этого разбирала статья «unsafe и FFI». Сертифицируемые применения закрывает Ferrocene — квалифицированный тулчейн для ISO 26262 и IEC 61508.
Остальная карта: где Rust уже стал нормой
- Инструменты командной строки. clap для аргументов, ratatui для TUI,
indicatifдля прогресса. Один статически слинкованный файл без рантайма — причина, по которойripgrep,fd,bat,eza,zoxide,difftasticнаписаны именно так. - Инструменты для других экосистем.
ruffиuv(Python),swc,oxc,biome,rolldown(JavaScript),turbopack. Показательно: узкое место инструментария динамических языков закрывают на Rust, потому что там время запуска и потребление памяти видны разработчику каждую минуту. - Данные и аналитика. Polars как замена pandas, Apache Arrow и DataFusion как движок запросов, rayon для параллельных итераторов в одну строку. Смежные вопросы форматов — в статье «Хранение и форматы».
- Инфраструктура. Firecracker (микровиртуальные машины AWS), TiKV, Vector, linkerd2-proxy, Meilisearch, Qdrant, InfluxDB. Здесь Rust выигрывает по той же причине, что и на прокси: память и хвосты задержек. Контекст — треки «Операционные системы» (например, «Виртуализация и контейнеры») и «Производительность систем».
- Криптография и сеть. rustls как TLS-стек без OpenSSL, quinn и
s2n-quicдля QUIC. Теория — в статьях «TLS» и «UDP и QUIC». - Блокчейн. Solana, Substrate, Foundry, reth — целый пласт индустрии на Rust; см. трек Web3 и статью «Смарт-контракты».
- Игры и графика. Bevy как ECS-движок, wgpu как переносимый графический API (он же лежит в основе WebGPU в Firefox). Готовность — «можно делать инди», а не «можно делать AAA».
- Машинное обучение. candle, burn, привязки
tch-rsиort. Обучение моделей живёт в Python и там останется; ниша Rust — инференс и обвязка (tokenizers,safetensorsот HuggingFace написаны на Rust), то есть та часть, что описана в статье «MLOps». - GUI и десктоп. Tauri (веб-UI, нативный бэкенд),
egui,iced,slint. Состояние честно отражает сайт areweguiyet.com: «ещё нет».
Встраивание в чужой стек: самый дешёвый способ принести Rust в компанию
Полное переписывание сервиса ставит под удар всё сразу. Практически всегда лучше работает точечная замена узкого места с сохранением остального стека.
Python. PyO3 и maturin дают нативный модуль, который импортируется как обычный пакет и ставится через pip. Так устроены pydantic-core, polars, tokenizers, orjson, cryptography.
use pyo3::prelude::*;
/// Тяжёлая функция, ради которой всё затевалось.
#[pyfunction]
fn count_ngrams(py: Python<'_>, text: &str, n: usize) -> PyResult<usize> {
// Отпускаем GIL на время счёта: пока Rust работает, другие потоки Python живут.
// Забыть эту строку — самая частая причина «переписали на Rust, а быстрее не стало».
Ok(py.allow_threads(|| text.as_bytes().windows(n).filter(|w| !w.contains(&b' ')).count()))
}
#[pymodule]
fn fasttext_utils(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(count_ngrams, m)?)?;
Ok(())
}
Ключевой момент — граница. Выгодно переносить крупные куски работы: вызов Rust из Python в цикле по миллиону элементов съест выигрыш на маршалинге. Переносите цикл целиком, а не тело цикла. Дальше — сборка колёс под все платформы (maturin умеет это в GitHub Actions), и с этого момента пользователи вашего пакета даже не знают, что внутри Rust.
Node.js — napi-rs по той же схеме. C и C++ — bindgen для заголовков C и cxx для безопасного двустороннего моста с C++. Мобильные приложения — UniFFI от Mozilla генерирует биндинги для Kotlin и Swift, что позволяет держать одну реализацию бизнес-логики на две платформы. Go — технически возможно через cgo, практически болезненно: разные модели рантайма и стеков, накладные расходы на переход и потеря главного преимущества Go — простой сборки; чаще выгоднее вынести Rust в отдельный сервис.
Крупные публичные истории, на которые можно ссылаться в разговоре с руководством: Android снизил долю уязвимостей памяти с 76% до 24% по мере роста доли Rust в новом коде; ядро Linux приняло Rust как второй язык начиная с версии 6.1; Microsoft переписывает части Windows; Cloudflare, AWS (Firecracker, s2n-quic), Dropbox, Figma, Discord используют Rust в проде годами. Аргумент «это экзотика» в 2026 году не работает — работает аргумент «это дорого в найме», и он честный.
Честная цена
Компиляция
Это главная ежедневная боль, и её нельзя заговорить. Причины системные: мономорфизация дженериков порождает много кода, процедурные макросы (serde, sqlx) выполняются при каждой сборке, LLVM тщательно оптимизирует, компоновка большого бинаря небыстрая. Порядок величин для типичного сервиса на axum, sqlx и tokio (около 300–400 крейтов в графе) на восьми ядрах:
| Операция | Время | Как сократить |
|---|---|---|
cargo check инкрементально |
1–5 с | это ваш основной цикл; IDE делает то же через rust-analyzer |
cargo build инкрементально (debug) |
5–30 с | mold/lld вместо стандартного компоновщика; debug = 0 |
cargo build с нуля (debug) |
1–3 мин | sccache для кеша между ветками и в CI |
cargo build --release с нуля |
3–10 мин | в CI кешируйте ~/.cargo и target, собирайте release только на выкатку |
cargo test большого проекта |
зависит | cargo nextest run — параллельный запуск и внятный вывод |
Что действительно помогает, по убыванию эффекта: не тянуть лишнее (default-features = false, cargo tree --duplicates, отказ от крейта ради трёх строк); быстрый компоновщик (mold на Linux нередко срезает половину времени инкрементальной сборки); cargo check вместо build в цикле правок; workspace вместо монокрейта, чтобы перекомпилировался только изменённый пакет; cargo build --timings, чтобы увидеть, кто именно съедает минуты, а не гадать. Оптимизировать зависимости, но не свой код, в dev-профиле — полезный трюк для тестов на реальных данных:
[profile.dev]
opt-level = 0 # ваш код: быстро компилируется, медленно работает
debug = 1 # таблицы строк хватает для бэктрейсов, а линковка заметно быстрее
[profile.dev.package."*"]
opt-level = 2 # зависимости оптимизируются один раз и потом просто лежат в кеше
Методику измерения этих улучшений — что мерить, как не обмануться шумом — даёт статья «Честный бенчмаркинг».
Кривая обучения
Реалистичные сроки для опытного инженера: две-три недели до «пишу и оно компилируется», два-три месяца до «не борюсь с заимствованиями», полгода-год до уверенного проектирования API с временами жизни и трейтами. Самый крутой участок — не синтаксис, а смена модели: необходимость заранее решать, кто чем владеет, тогда как в языках с GC этот вопрос просто не задаётся. Возвращайтесь к статьям «Владение и заимствование» и «Времена жизни» не как к теории, а как к справочнику — большинство «непонятных ошибок» на третьем месяце оказываются одним из пяти базовых случаев.
Отдельно про компилятор: умение читать rustc — половина навыка. Сообщение почти всегда содержит note: с причиной и help: со следующим шагом, а rustc --explain E0502 разворачивает код ошибки в статью с примерами. Инженер, который читает ошибку до конца, учится в разы быстрее того, кто ищет её текст в поиске.
Когда Rust избыточен
Прямым текстом, чтобы не пришлось выяснять это на проекте:
- Прототип и исследование. Требования меняются ежедневно, код живёт неделю. Стоимость строгости не окупается.
- Скрипты и склейка. Bash, Python и Go написаны для этого; Rust даст вам полминуты компиляции ради двадцати строк.
- CRUD-сервис средней нагрузки со сроками. Если p99 в 50 мс никому не нужен, а нужен эндпоинт к пятнице, выбирайте то, на чём команда пишет быстро.
- Команда без опыта и без времени на обучение. Три месяца просадки скорости — реальная цена, её надо планировать, а не обнаруживать.
- Домены с зависимостью от зрелых SDK. Если ключевая интеграция существует только под Java, спор окончен.
- Тяжёлый GUI и мобильный UI. Экосистема пока не даёт того, что дают нативные наборы и Flutter.
Симметрично: если у вас сетевой узел с миллионом соединений, прошивка на 64 КБ ОЗУ, песочница для чужого кода, ускоритель для Python, парсер бинарного формата или сервис, где паузы GC уже стоили денег, — Rust не «модный выбор», а профильный инструмент.
Ресурсы: что читать в следующие полгода
Официальное, бесплатное, обязательное
- The Rust Programming Language — «книга», канонический вход; есть русский перевод, но он отстаёт, сверяйтесь с оригиналом. Рядом — Rust by Example с короткими запускаемыми примерами.
- Документация
std— читайте разделами, а не по поиску: примеры там образцовые. Точные ответы про язык — в The Rust Reference, про сборку — в The Cargo Book. - Rust API Guidelines — чеклист перед публикацией крейта; Rust Design Patterns — идиомы и антипаттерны с объяснением.
- The Rustonomicon — правила
unsafe; читать после статьи «unsafe и FFI». - Домены: Async Book и Tokio Tutorial, The Embedded Rust Book и Discovery, Rust and WebAssembly.
- The rustc Error Index — расшифровка кодов ошибок; то же самое локально через
rustc --explain.
Книги, которые стоят своих денег
- Jon Gjengset. Rust for Rustaceans — nostarch.com/rust-rustaceans. Лучшее «что дальше» после книги: типы, вариантность,
unsafe, проектирование API. Его канал Crust of Rust — многочасовые разборы вживую. - Mara Bos. Rust Atomics and Locks — marabos.nl/atomics/, доступна бесплатно. Единственная книга, после которой модель памяти и
Orderingперестают быть заклинанием. - Jim Blandy и др. Programming Rust, 2-е изд. (O’Reilly) — самое подробное описание языка с системным уклоном.
- Luca Palmieri. Zero To Production In Rust — zero2prod.com. Один сквозной продовый сервис: тесты, миграции, телеметрия, деплой. Идеальное дополнение к статье «Прод на Rust».
- Ken Youens-Clark. Command-Line Rust (O’Reilly) — упражнения через переписывание утилит Unix.
Держать руку на пульсе
- This Week in Rust — еженедельный дайджест, самый эффективный способ следить за экосистемой; Rust Blog и Inside Rust — релизы и работа команд; Rust Users Forum — место, где на «почему это не компилируется» отвечают по существу.
- Навигация и состояние доменов: blessed.rs, lib.rs, cheats.rs, arewewebyet.org, areweguiyet.com, arewegameyet.rs, arewelearningyet.com.
Практика важнее чтения. Порядок задач, который работает: (1) утилита CLI на 200 строк с clap и тестами; (2) HTTP-сервис с базой, миграциями и tracing, выкаченный в контейнере; (3) перенос узкого места из вашего рабочего проекта в Rust через PyO3 или napi-rs — с замером до и после; (4) что-то из «странного»: прошивка мигающего светодиода, wasm-плагин, парсер бинарного формата на nom; (5) публикация своего крейта на crates.io с документацией и doc-тестами по правилам из статьи «Тестирование и документация». Пятый пункт закрывает больше пробелов, чем первые четыре: публичный API заставляет думать про времена жизни, обратную совместимость и сообщения об ошибках.
Мини-итог
- Стандартная библиотека Rust маленькая намеренно; из-за этого выбор зависимостей — часть архитектуры, а не деталь.
cargo-deny,cargo-auditиcargo tree --duplicatesставятся в CI в первый день. - Веб-бэкенд, CLI, сетевая инфраструктура и инструментарий — зрелые слои: здесь Rust скучен и предсказуем. GUI, фронтенд целиком и обучение ML-моделей — нет.
- «WASM» — это два разных мира: браузерный
wasm32-unknown-unknownс wasm-bindgen и серверный WASI с песочницей и плагинами. Через границу проходят только числа, всё остальное копируется через линейную память. no_std— не урезанный Rust, а другой слой:coreвсегда,allocпри наличии аллокатора,stdпри наличии ОС. Библиотеки полезно писать наcoreс фичами.- Самый дешёвый путь внедрения — не переписывание, а точечная замена узкого места через PyO3, napi-rs, cxx или UniFFI, с замером до и после.
- Цена реальна: минуты сборки, месяцы обучения, узкий рынок найма. Есть большой класс задач, где Rust — неправильный ответ, и умение это сказать — часть профессионализма.
- Умение читать сообщения
rustc(доnote:иhelp:, плюс--explain) ускоряет обучение сильнее любого курса.
Источники
- Официальная документация: The Book, Reference, Cargo Book, Rustonomicon, Error Index, Async Book, Embedded Rust Book, Rust and WebAssembly
- Крейты и инструменты: axum, actix-web, sqlx, diesel, tower, wasm-bindgen, wasmtime, embedded-hal, Embassy, RTIC, probe-rs, PyO3, maturin, UniFFI, cxx
- Цепочка поставок: RustSec, cargo-vet, cargo-deny
- Индустриальные кейсы: Discord: Why Discord is switching from Go to Rust, Cloudflare Pingora, Google: Eliminating Memory Safety Vulnerabilities at the Source, Rust for Linux
- Книги: Jon Gjengset, Rust for Rustaceans; Mara Bos, Rust Atomics and Locks; Luca Palmieri, Zero To Production In Rust; Jim Blandy et al., Programming Rust, 2nd ed., O’Reilly
Что дальше
Трек «Rust» закончен. Мы прошли путь от обзора языка и его философии и инструментария через основы, владение, времена жизни, типы и трейты, ошибки, коллекции и итераторы, конкурентность, асинхронность, unsafe и FFI, тестирование и прод — к карте экосистемы. Дальше стоит выбирать не следующую статью, а следующий контекст:
- Понять машину, на которой всё это исполняется — трек «Системное программирование» на портале: память, процессы, системные вызовы с точки зрения программиста, который пишет близко к железу. Прямое продолжение статей о владении и об
unsafe. - Разобраться с платформой — Операционные системы, особенно «Управление памятью» и «Системные вызовы и IPC»: то, что Rust даёт вам в виде типов, ядро реализует в виде страниц и дескрипторов.
- Научиться доказывать, что стало быстрее — Производительность систем и «Честный бенчмаркинг». Переписать на Rust и не измерить — самый дорогой способ ошибиться.
- Уйти во встраиваемое — Встраиваемые системы: железо, прерывания, питание, протоколы.
no_stdиз этой статьи там станет ежедневной средой. - Достроить бэкенд-картину — Базы данных, Распределённые системы и DevOps: язык — меньшая часть продового сервиса.
- Сравнить модели — Go с его горутинами и сборщиком мусора, Elixir с моделью акторов и отказоустойчивостью: понимание того, что именно вы выбрали в Rust, приходит от сравнения с альтернативой.
Общая карта портала и разумный порядок изучения треков — в «Дорожной карте».