У Rust очень сильная репутация языка, который даёт скорость, безопасность памяти и инженерную строгость. Из-за этого его часто пытаются воспринимать как универсальный следующий шаг для backend: если хочется надёжнее и быстрее — значит, надо брать Rust.
В реальности всё сложнее. Rust действительно может дать очень хороший результат в backend, особенно там, где важны контроль над ресурсами, производительность и предсказуемый runtime behavior. Но у него есть и цена: сложнее порог входа, более высокая стоимость изменений, меньше готового прикладного ecosystem comfort по сравнению с более зрелыми backend-стеками.
Эта статья — первая в серии про Rust в backend. Не про ownership как учебную тему и не про полный обзор экосистемы, а про спокойный инженерный вопрос: где Rust в backend действительно уместен, а где его легко выбрать раньше, чем задача до него доросла.
В статье
- Что Rust даёт backend-системам
- Кто уже использует Rust в backend и зачем
- Как это выглядит: Go vs Rust
- Какая цена у этого выбора
- Rust, Go, Java: сравнение для backend
- Когда выбирать Rust: диаграмма решения
- Когда лучше не форсировать
Что Rust даёт backend-системам
Предсказуемый runtime без сборщика мусора
В Go и Java сборщик мусора периодически останавливает выполнение — пусть на миллисекунды, но для некоторых задач это критично. Сервис, обрабатывающий финансовые транзакции или real-time поток данных, не может позволить себе паузу в 10–50 мс в произвольный момент.
Rust управляет памятью через ownership и borrowing на этапе компиляции. В runtime нет GC, нет пауз, нет непредсказуемых всплесков latency. Каждая аллокация явна, каждое освобождение детерминировано.
Компактные runtime и Docker-образы
Rust компилируется в нативный бинарник без runtime-зависимостей. С musl и статической линковкой финальный Docker-образ на базе scratch занимает 5–15 MB. Для сравнения: минимальный Go-образ — 10–20 MB, Java с JRE — от 150 MB.
Это не просто экономия места. Меньший образ — это быстрее pull при деплое, меньше attack surface для безопасности, меньше потребления на кластере при десятках реплик.
Контроль над concurrency
Rust не даёт скомпилировать код с data race — система типов это запрещает на уровне компилятора. В Go гонки данных — runtime-проблема, которую ловит -race детектор (и то не всегда). В Java — это ещё сложнее.
Для network-heavy сервисов, обрабатывающих тысячи одновременных соединений, это означает: если код скомпилировался — целый класс concurrency-багов просто невозможен.
Эффективность на ограниченных ресурсах
Rust-сервис потребляет значительно меньше памяти, чем аналогичный на Go или Java. Типичный HTTP-сервис на Axum в idle занимает 2–5 MB RSS. Аналогичный на Go — 10–20 MB, на Spring Boot — 150–300 MB.
Это особенно заметно, когда нужно запускать десятки микросервисов на ограниченном кластере, или когда сервис работает как sidecar/agent на каждой ноде.
Кто уже использует Rust в backend и зачем
Discord: p99 latency с 200 мс до 10 мс
Discord переписал свой сервис Read States с Go на Rust. Read States отслеживает, какие сообщения прочитал каждый пользователь — это сотни миллионов записей в памяти, которые обновляются при каждом действии.
Проблема была конкретной: Go GC сканировал огромную LRU-кеш-структуру каждые 2 минуты, создавая latency spikes до 200 мс на p99. Тюнинг GC-параметров (GOGC) помогал частично, но не решал фундаментальную проблему — GC всё равно должен был обходить миллионы указателей.
После миграции на Rust p99 latency упал до 10 мс и перестал зависеть от размера кеша. Потребление CPU тоже снизилось — без GC процессор не тратит циклы на фоновое сканирование памяти.
Ключевое: проблемой был не Go в целом, а конкретный паттерн — большие long-lived in-memory структуры с указателями, где GC становится bottleneck.
Cloudflare: 25 миллионов HTTP-запросов в секунду на edge
Cloudflare обрабатывает порядка 25 млн HTTP-запросов в секунду на своей edge-сети из 300+ дата-центров. Для этого уровня нагрузки каждый мегабайт памяти и каждая микросекунда latency на ноде имеют значение — они умножаются на тысячи серверов.
Rust используется для ключевых edge-компонентов: HTTP-прокси (Pingora, который заменил nginx), WAF-движок, DNS-резолвер. Pingora, по данным Cloudflare, потребляет на 70% меньше CPU и на 67% меньше памяти по сравнению с предыдущим решением на C.
Причина выбора прагматичная: на edge нужна максимальная плотность — больше throughput на меньше ресурсов. Rust даёт производительность на уровне C, но с memory safety, что критично для сервисов, обрабатывающих недоверенный трафик.
Figma: real-time collaborative editing без лишних latency spikes
Figma написала на Rust свой multiplayer-сервер, обрабатывающий real-time синхронизацию между пользователями — operational transforms при одновременном редактировании одного документа.
Для такого класса задач важен не только средний response time, но и отсутствие неожиданных выбросов latency: collaborative editing очень чувствителен к любым задержкам в критическом пути. В официальном разборе Figma акцент делает не на «магических числах», а на том, что переход на Rust дал improvement примерно на порядок по сравнению с предыдущей реализацией.
Этот кейс хорошо показывает общий принцип: Rust особенно уместен там, где backend живёт в tight latency budget и даже редкие runtime-паузы начинают заметно влиять на пользовательский опыт.
Общий паттерн
Во всех этих случаях Rust выбирали не потому, что он «лучший язык», а потому, что задача упиралась в конкретное ограничение: GC-паузы, плотность нагрузки, latency-бюджет. Если таких ограничений нет — выбор в пользу Rust менее очевиден.
Как это выглядит: Go vs Rust
Лучший способ оценить порог входа — сравнить один и тот же сервис на двух языках. Вот HTTP-сервис с JSON health check и graceful shutdown.
Go — 25 строк, стандартная библиотека:
package main
import (
"context"
"encoding/json"
"net/http"
"os/signal"
"syscall"
)
type Status struct {
Service string `json:"service"`
Healthy bool `json:"healthy"`
}
func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Status{Service: "my-service", Healthy: true})
})
srv := &http.Server{Addr: ":3000"}
go srv.ListenAndServe()
<-ctx.Done()
srv.Shutdown(context.Background())
}
Нет внешних зависимостей. Любой Go-разработчик прочитает это за минуту.
Rust (Axum) — тот же функционал, но другой уровень явности:
use axum::{routing::get, Json, Router};
use serde::Serialize;
use tokio::net::TcpListener;
use tokio::signal;
#[derive(Serialize)]
struct Status {
service: &'static str,
version: &'static str,
healthy: bool,
}
async fn health() -> Json<Status> {
Json(Status {
service: "my-service",
version: env!("CARGO_PKG_VERSION"),
healthy: true,
})
}
async fn root() -> &'static str {
"OK"
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(root))
.route("/health", get(health));
let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
println!("listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
}
async fn shutdown_signal() {
signal::ctrl_c().await.expect("failed to listen for ctrl+c");
println!("shutting down gracefully");
}
Зависимости в Cargo.toml:
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
Три внешних зависимости, 40 строк. Код компактный, но требует понимания async/await, lifetime ('static), макросов (derive), ownership (Json забирает значение). Go-разработчик прочитает Go-версию за минуту, Rust-версию — за десять, с вопросами «зачем #[tokio::main]?», «что такое &'static str?», «почему unwrap?».
Это не значит, что Rust хуже. Это значит, что порог входа — реальный, и его нужно закладывать в стоимость.
Какая цена у этого выбора
Rust почти никогда не бывает «бесплатным улучшением». Его цена проявляется в нескольких измерениях.
Порог входа и onboarding
Ownership, borrowing, lifetimes — это не просто «ещё одна концепция». Это фундаментально другая модель мышления о памяти и данных. Продуктивный Go-разработчик появляется через 2–4 недели. Для Rust — 2–4 месяца до уверенной работы с borrow checker без постоянной борьбы.
Простой пример. Передача строки в функцию — на Go и на Rust:
func process(s string) { fmt.Println(s) }
func main() {
name := "hello"
process(name)
fmt.Println(name) // работает — строки копируются неявно
}
fn process(s: String) { println!("{s}"); }
fn main() {
let name = String::from("hello");
process(name);
println!("{name}"); // ошибка компиляции: name moved в process()
}
В Go это «просто работает». В Rust нужно решить: передать по ссылке (&String), клонировать (.clone()), или перестроить архитектуру. Каждый раз. Для каждого значения. Это то, что делает первые месяцы с Rust медленными — не сложность задач, а постоянные переговоры с компилятором о владении данными.
Скорость прикладной разработки
CRUD-эндпоинт на Go пишется за 20 минут. На Rust — за час, с учётом борьбы с типами, трейтами и ошибками компиляции. Для инфраструктурного сервиса, который пишется один раз и работает годами, это приемлемо. Для прикладного API, который меняется каждую неделю — overhead ощутим.
Экосистема
Rust-экосистема для web активно растёт, но пока уступает Go и Java по зрелости. ORM, миграции, очереди, admin-панели — всё есть, но часто моложе, с меньшим community и менее стабильным API. Что-то приходится собирать из частей, где в Go или Spring есть готовое решение.
Стоимость архитектурных ошибок
В Rust переделка API или структуры данных обходится дороже — система типов строже, рефакторинг затрагивает больше кода. Архитектурные решения нужно продумывать тщательнее на старте, иначе стоимость изменений растёт быстрее, чем в более гибких языках.
Rust, Go, Java: сравнение для backend
| Критерий | Rust | Go | Java/Kotlin |
|---|---|---|---|
| Порог входа | Высокий (2–4 мес.) | Низкий (2–4 нед.) | Средний (1–2 мес.) |
| Docker-образ (min) | 5–15 MB | 10–20 MB | 150+ MB |
| Память idle HTTP-сервис | 2–5 MB | 10–20 MB | 150–300 MB |
| GC-паузы | Нет | Есть (< 1 мс обычно) | Есть (зависит от GC) |
| Compile-time safety | Максимальная | Средняя | Средняя |
| Экосистема для web | Растёт | Зрелая | Зрелая |
| Time-to-market | Медленнее | Быстро | Средне |
| Concurrency-баги | Невозможны (compile-time) | Runtime (-race) | Runtime |
Таблица не говорит, что Rust «лучше». Она показывает: Rust выигрывает по runtime-характеристикам и safety, но платит за это скоростью разработки и порогом входа.
Когда выбирать Rust: диаграмма решения
proxy, gateway, agent, ingestion"} B -->|Да| C{"Жёсткие требования
к latency / памяти?"} C -->|Да| D["✅ Rust — сильный кандидат"] C -->|Нет| E["⚖️ Rust возможен,
оцените стоимость vs выигрыш"] B -->|Нет| F{"Прикладной сервис
(API, CRUD, бизнес-логика)"} F -->|Да| G{"Команда знает Rust?"} G -->|Да| E G -->|Нет| H["🔧 Go / Java / текущий стек
быстрее и дешевле"] F -->|Нет| I{"Системный компонент?
CLI, SDK, embedded"} I -->|Да| D I -->|Нет| H
graph TD
A["Новый backend-сервис"] --> B{"Инфраструктурный уровень?
proxy, gateway, agent, ingestion"}
B -->|Да| C{"Жёсткие требования
к latency / памяти?"}
C -->|Да| D["✅ Rust — сильный кандидат"]
C -->|Нет| E["⚖️ Rust возможен,
оцените стоимость vs выигрыш"]
B -->|Нет| F{"Прикладной сервис
(API, CRUD, бизнес-логика)"}
F -->|Да| G{"Команда знает Rust?"}
G -->|Да| E
G -->|Нет| H["🔧 Go / Java / текущий стек
быстрее и дешевле"]
F -->|Нет| I{"Системный компонент?
CLI, SDK, embedded"}
I -->|Да| D
I -->|Нет| H
Когда лучше не форсировать
Rust легко переоценить, если смотреть на него как на «правильный» язык сам по себе.
Задача прикладная и типовая. REST API для мобильного приложения, админка, интеграция с внешним сервисом — здесь скорость разработки важнее скорости runtime. Go или Java/Kotlin дадут результат быстрее при сопоставимом качестве.
Команда эффективна в другом стеке. Переход на Rust — это инвестиция в месяцы обучения. Если текущий стек справляется с задачей, эта инвестиция может не окупиться.
Time-to-market критичен. Если продукт нужно запустить через месяц, Rust скорее всего замедлит. Не потому что он плох, а потому что требует больше аккуратности на каждом шаге.
Нет реальных ограничений по ресурсам. Если сервис живёт на выделенной ВМ с запасом по CPU и памяти, выигрыш от минимального footprint — теоретический. Он станет реальным, когда нагрузка вырастет, но к тому моменту можно будет переписать узкое место.
Антикейс: переписывание ради переписывания
Типичная ситуация: команда решает переписать внутренний CRUD-сервис с Go на Rust. Мотивация — «будет быстрее и надёжнее». Через три месяца: сервис работает, потребляет на 8 MB меньше памяти, отвечает на 3 мс быстрее. Но три месяца ушли вместо трёх недель, за это время конкуренты выкатили две фичи, а команда потратила бюджет на борьбу с lifetimes в слое сериализации.
Выигрыш реален, но не окупился. Прежний Go-сервис справлялся с нагрузкой, потреблял приемлемые ресурсы, и его мог поддерживать любой разработчик в команде. Rust дал лучший код — но не лучший результат для продукта.
В этих случаях Rust даст качественный код, но не обязательно лучший общий результат для продукта и команды.
Итог
Rust в backend действительно силён, но его сила раскрывается не в любой задаче одинаково. Это не универсальная замена другим языкам и не «следующая обязательная ступень». Это инструмент, который особенно хорош там, где backend уже соприкасается с системным уровнем, производительностью и жёсткими требованиями к runtime.
Если выбирать его под этот профиль задач, результат часто получается очень сильным. Если выбирать его просто «потому что он лучше», можно легко переплатить сложностью там, где она не окупается.
Что дальше в серии
Это первая часть цикла «Rust для backend». В следующих статьях — от теории к практике:
- Rust async runtime: Tokio изнутри — как устроен async в Rust, чем отличается от goroutines, когда async даёт выигрыш
- Rust web-фреймворки: Axum vs Actix — сравнение, примеры, выбор под задачу
- Rust в Docker: минимальные образы — multi-stage builds, musl, cross-compilation, CI
- Rust в production: паттерны для надёжного backend — config, graceful shutdown, tracing, OpenTelemetry
Документация
Реальные кейсы и первоисточники
- Why Discord is switching from Go to Rust — официальный разбор Discord про Read States, GC и миграцию на Rust
- How we built Pingora — официальный разбор Cloudflare про Pingora, CPU и memory footprint
- Rust in production at Figma — опыт Figma по использованию Rust в production
- Making multiplayer more reliable — о надёжности multiplayer-системы Figma
База по языку и экосистеме
- The Rust Programming Language — официальная книга по языку
- Tokio — async runtime для network-сервисов
- Axum — web-фреймворк на базе Tower
- Are we web yet? — обзор зрелости Rust-экосистемы для web
- Zero To Production In Rust — книга про production backend на Rust

Комментарии