Rust или Go для бэкенда: как мы выбираем
Оба языка современные, быстрые и компилируемые. Настоящий вопрос не в том, какой «лучше», а в том, какой подходит под вашу задачу. Вот как мы решаем на практике.
Мы пишем production-системы и на Rust, и на Go, и наш собственный продукт работает на обоих: Go мы используем там, где важна скорость итераций, а Rust там, где задержка и безопасность памяти не обсуждаются. Поэтому, когда нас спрашивают, какой язык выбрать для нового бэкенда, мы отвечаем: зависит от задачи, а тот, кто отвечает одним словом, что-то вам продаёт.
В этом тексте мы сравниваем их так, как этого не хватает в публичных спорах. Здесь нет гонки бенчмарков, зато есть практические компромиссы, которые на реальных проектах и определяют выбор.
Что у них общего
Начнём с того, в чём они сходятся, а сходятся они во многом. Оба компилируются в нативный код, оба достаточно быстры, чтобы язык редко становился узким местом, оба собираются в один статический бинарник, который тривиально развернуть в контейнере, и у обоих первоклассная поддержка конкурентности и здоровая экосистема. Любой из них будет обоснованным выбором для бэкенд-сервиса. Выбрав «не тот», вы пожалеете об этом куда меньше, чем обещают интернет-споры.
Где выигрывает Go
Go оптимизирован прежде всего под одно: сделать команду продуктивной и сохранить её такой. Язык настолько компактен, что его можно выучить за неделю, стандартная библиотека покрывает большую часть того, что нужно сервису, а горутины делают конкурентный код доступным, а не пугающим. Компиляция почти мгновенная, и цикл обратной связи остаётся коротким.
- Типовые бэкенд-сервисы: REST- и gRPC-API, микросервисы, сервисы оркестрации, воркеры.
- Команды, которым нужно быстро двигаться и быстро вводить в работу новых инженеров.
- Сетевые сервисы с высокой конкурентностью, где горутины раскрываются в полной мере, а самую сложную работу берёт на себя рантайм.
- Всё, где простота и читаемость важнее, чем выжать последние проценты производительности.
Обратная сторона: в Go есть сборка мусора. Для подавляющего большинства сервисов это не проблема: сборщик у Go отличный. Но это означает редкие паузы и меньше контроля над размещением данных в памяти, а для определённого класса задач, чувствительных к задержкам, это важно.
Где выигрывает Rust
Rust даёт производительность уровня C, безопасность памяти и потоков, проверяемую на этапе компиляции, и никакого сборщика мусора. Borrow checker, который так раздражает новичков, как раз и не даёт выпустить в продакшен целые классы ошибок: use-after-free, гонки данных, разыменование null. Когда важны и корректность, и скорость, эта гарантия стоит более крутой кривой обучения.
- Системное и сетевое ПО: протоколы, прокси, агенты, data plane.
- Конвейеры реального времени, где пауза сборщика мусора недопустима, например живое медиа.
- Критичные к производительности компоненты внутри большой системы, часто через FFI или в виде отдельного узкого сервиса.
- WebAssembly, где Rust поддерживается полноценно и позволяет доставлять в браузер код с нативной скоростью.
Цена вполне реальна: Rust дольше учить, на нём дольше писать, и он дольше компилируется. Команда, которая только начинает с ним работать, первые несколько месяцев будет медленнее. В подходящей области эти вложения окупаются, а в неподходящей превращаются в чистые накладные расходы.
Как мы выбираем на самом деле
На нашей собственной платформе разделение сложилось естественно: Go мы используем там, где важнее всего скорость итераций и читаемость, а Rust там, где задержка и безопасность памяти не обсуждаются. Мы не выбирали сторону, а подбирали инструмент под задачу.
Берите Rust, когда падение, гонка данных или пауза сборщика мусора недопустимы. Для почти всего остального берите Go. Большинство бэкендов как раз и относится ко «всему остальному».
Полезное правило по умолчанию: начинайте новый бэкенд на Go, если у вас нет конкретной причины поступить иначе, и точечно вводите Rust для компонентов, которым он действительно нужен. Не обязательно выбирать один язык на всю систему. Вполне разумна архитектура, где сервис на Go использует компонент на Rust через FFI или небольшой сервис на Rust стоит за API на Go.
Если вы делаете этот выбор для реального проекта и хотите услышать второе мнение от тех, кто выпускает продукты на обоих языках, мы с удовольствием это обсудим.