Go или Rust: подробное сравнение и какой язык выбрать
Чем Go и Rust отличаются на практике: четырнадцать критериев, шесть различий, которые чувствуются в работе, какой язык под какую задачу, одни и те же задачи на обоих и частые ошибки.
Коротко
Go и Rust — оба компилируемые, быстрые и безопасные по памяти, но сделаны для разного. Go ставит на простоту: маленький язык, сборщик мусора, горутины, компиляция за секунды и команда, которая становится продуктивной за недели, — выбор по умолчанию для веб-сервисов, API и облачных инструментов. Rust ставит на контроль: без сборщика мусора, владение проверяет компилятор, в безопасном коде нет гонок данных, задержки предсказуемы — выбор для системного ПО, высокопроизводительных движков, WebAssembly и мест, где на счету каждая миллисекунда и мегабайт. Для типичного бэкенда Go быстрее доведёт до продакшена; Rust окупается там, где паузы или память Go становятся проблемой.
Коротко: что выбрать
Если вы строите API, веб-сервис, бэкенд бота, обработчик очередей или облачный инструмент — берите Go. Язык осваивается быстро, код читается одинаково в любой команде, компиляция занимает секунды, а паузы сборщика мусора достаточно коротки почти для любой веб-нагрузки.
Если вы строите движок базы данных, прокси, который никогда не должен останавливаться, библиотеку для WebAssembly, прошивку или код, который выполнится миллиарды раз, — берите Rust. Платите крутым порогом входа и медленной компиляцией, а получаете максимум скорости, минимум памяти и ошибки, пойманные до запуска.
- Веб-сервисы и API — Go
- Системы и каждая миллисекунда — Rust
- Оба безопасные и компилируемые
Go и Rust: подробное сравнение
Четырнадцать критериев рядом. Скорость зависит от кода, поэтому в таблице — взаимное положение, а не бенчмарки.
| Критерий | Go | Rust |
|---|---|---|
| Происхождение | Google, 2009; версия 1.0 — в 2012-м | Mozilla, 2010; версия 1.0 — в 2015-м; теперь Rust Foundation |
| Философия | простота и один очевидный способ | контроль и абстракции без накладных расходов |
| Память | сборщик мусора с короткими паузами | владение, без сборщика |
| Гонки данных | возможны; детектор гонок находит их в тестах | невозможны в безопасном коде |
| Параллельность | горутины и каналы, встроены | потоки и async с Tokio |
| Ошибки | значения и if err != nil | Result и оператор ? |
| Скорость | высокая | наивысшая, близко к C++ |
| Задержки | стабильные, с редкими короткими паузами | полностью предсказуемые |
| Расход памяти | умеренный | минимальный |
| Компиляция | секунды | заметно медленнее |
| Порог входа | низкий: недели | высокий: месяцы |
| Экосистема | облако и веб: Docker, Kubernetes, Terraform | системы, WebAssembly, инструменты: ripgrep, Tauri, Rolldown |
| Найм | проще | сложнее, специалистов меньше |
| Лучше всего для | API, веб-сервисов, обработчиков, облачных инструментов | движков, прокси, WebAssembly, встраиваемых систем |
6 различий, которые чувствуются в работе
-
Первый месяц
В Go новичок выпускает функции в первые недели; в Rust первые недели уходят на споры с проверкой заимствований.
-
Скорость итераций
Go компилирует сервис за секунды; большой проект на Rust после правки может собираться минуты.
-
Где ловятся ошибки
Rust ловит больше при компиляции — гонки, пустые значения, необработанные ошибки; Go часть оставляет тестам.
-
Чтение чужого кода
Код на Go везде выглядит одинаково; код на Rust зависит от того, как автор использует трейты и макросы.
-
Под экстремальной нагрузкой
Когда паузы сборщика мусора начинают проявляться в самых медленных запросах, Rust убирает их полностью.
-
Выкладка
Оба дают один бинарник без зависимостей; Go собирает под любую платформу одной переменной.
Какой язык брать под задачу
Двенадцать типичных проектов с рекомендацией и причиной.
| Задача | Брать | Почему |
|---|---|---|
| REST API или веб-сервис | Go | быстрая разработка, сильная стандартная библиотека |
| Бэкенд бота или обработчик очередей | Go | горутины и простой код |
| Микросервисы в команде | Go | легко нанимать и читать код друг друга |
| Облачные и DevOps-инструменты | Go | родина Docker и Kubernetes |
| Утилита командной строки | любой | на Go быстрее написать, на Rust — быстрее работает |
| Нагруженный прокси или шлюз | Rust | без пауз и с минимумом памяти |
| Движок базы или поиска | Rust | контроль над каждым байтом |
| WebAssembly в браузере | Rust | маленькие и быстрые модули без среды выполнения |
| Встраиваемые устройства | Rust | работает без операционной системы |
| Настольное приложение с веб-интерфейсом | Rust | Tauri: лёгкая альтернатива Electron |
| MVP стартапа | Go | скорость разработки важнее последнего процента скорости |
| Код со строгими требованиями к надёжности | Rust | компилятор доказывает больше |
Одни задачи на Go и Rust: 6 примеров
Параллельная работа, ошибки и полиморфизм на обоих языках. Go 1.26 проверен gofmt и go vet, Rust 1.97 собран с предупреждениями как ошибками; вывод — в комментариях.
Параллельная работа: Go
По горутине на текст и WaitGroup, которая ждёт их все.
package main
import (
"fmt"
"strings"
"sync"
)
// считаем слова в нескольких текстах сразу: по горутине на текст
func main() {
texts := []string{"дубовый стол и дубовый стул", "сосновая полка", "ореховый стол для домашнего офиса"}
counts := make([]int, len(texts))
var wg sync.WaitGroup
for i, t := range texts {
wg.Go(func() { // Go 1.25+: WaitGroup.Go запускает горутину и учитывает её
counts[i] = len(strings.Fields(t))
})
}
wg.Wait()
fmt.Println(counts) // [5 2 5]
}
Параллельная работа: Rust
Потоки в области видимости: каждый получает свою ячейку, и компилятор доказывает, что гонки нет.
use std::thread;
// то же на Rust: потоки в области видимости; компилятор доказывает, что гонок данных нет
fn main() {
let texts = ["дубовый стол и дубовый стул", "сосновая полка", "ореховый стол для домашнего офиса"];
let mut counts = [0; 3];
thread::scope(|s| {
for (count, text) in counts.iter_mut().zip(texts) {
s.spawn(move || *count = text.split_whitespace().count());
}
});
println!("{counts:?}"); // [5, 2, 5]
}
Ошибки: Go
Ошибка — значение: её оборачивают контекстом и проверяют через errors.Is.
package main
import (
"errors"
"fmt"
"strconv"
)
var ErrTooBig = errors.New("слишком много товаров")
// ошибка — обычное значение: её возвращают и проверяют явно
func parseQty(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("количество %q: %w", s, err)
}
if n > 100 {
return 0, ErrTooBig
}
return n, nil
}
func main() {
for _, in := range []string{"3", "abc", "500"} {
n, err := parseQty(in)
if errors.Is(err, ErrTooBig) {
fmt.Println("превышен лимит")
continue
}
fmt.Println(n, err)
}
// 3 <nil>
// 0 количество "abc": strconv.Atoi: parsing "abc": invalid syntax
// превышен лимит
}
Ошибки: Rust
Ошибка — часть типа, ? передаёт её выше, а match обязан обработать каждый вариант.
use std::num::ParseIntError;
enum QtyError {
NotANumber(ParseIntError),
TooBig,
}
// Result: ошибка — часть типа, и компилятор не даст её проигнорировать
fn parse_qty(s: &str) -> Result<u32, QtyError> {
let n: u32 = s.parse().map_err(QtyError::NotANumber)?;
if n > 100 {
return Err(QtyError::TooBig);
}
Ok(n)
}
fn main() {
for input in ["3", "abc", "500"] {
match parse_qty(input) {
Ok(n) => println!("{n}"),
Err(QtyError::TooBig) => println!("превышен лимит"),
Err(QtyError::NotANumber(e)) => println!("не число: {e}"),
}
}
// 3
// не число: invalid digit found in string
// превышен лимит
}
Полиморфизм: интерфейсы Go
Тип выполняет интерфейс, просто имея метод, — без объявлений.
package main
import "fmt"
// интерфейс выполняется неявно: достаточно иметь нужный метод
type Shipping interface {
Price(kg float64) float64
}
type Courier struct{ Base float64 }
type Pickup struct{}
func (c Courier) Price(kg float64) float64 { return c.Base + 50*kg }
func (Pickup) Price(float64) float64 { return 0 }
func main() {
options := []Shipping{Courier{Base: 300}, Pickup{}}
for _, o := range options {
fmt.Printf("%T: %.0f\n", o, o.Price(2))
}
// main.Courier: 400
// main.Pickup: 0
}
Полиморфизм: трейты Rust
Трейт реализуется явно, а динамический вызов виден в типе — Box<dyn Shipping>.
// трейт реализуется явно, и компилятор проверяет каждую реализацию
trait Shipping {
fn price(&self, kg: f64) -> f64;
}
struct Courier {
base: f64,
}
struct Pickup;
impl Shipping for Courier {
fn price(&self, kg: f64) -> f64 {
self.base + 50.0 * kg
}
}
impl Shipping for Pickup {
fn price(&self, _kg: f64) -> f64 {
0.0
}
}
fn main() {
let options: Vec<(&str, Box<dyn Shipping>)> =
vec![("Курьер", Box::new(Courier { base: 300.0 })), ("Самовывоз", Box::new(Pickup))];
for (name, o) in &options {
println!("{name}: {:.0}", o.price(2.0));
}
// Курьер: 400
// Самовывоз: 0
}
Частые ошибки при выборе
-
Rust для обычного API
Месяцы обучения и медленные сборки ради выигрыша, которого пользователи не заметят.
-
Go там, где паузы недопустимы
Для жёсткого реального времени и огромных объёмов памяти сборщик становится узким местом.
-
Выбирать по бенчмаркам
В веб-сервисе время обычно уходит на базу и сеть, а не на язык.
-
Забыть про команду
Язык, который в команде некому поддерживать, — риск больше любой разницы в скорости.
-
Переписать всё сразу
Переносите на Rust только медленную часть; остальное может остаться на Go.
Вопросы о Go и Rust
Что быстрее: Go или Rust?
Rust обычно быстрее и расходует меньше памяти, но для веб-сервиса разница часто незаметна: время уходит на базу и сеть.
Что проще выучить?
Go — с большим отрывом: маленький язык, на который уходят недели. Rust требует месяцев из-за владения и времён жизни.
На чём писать бэкенд?
Для большинства API и сервисов — Go. Rust — для частей, где важна каждая миллисекунда и мегабайт.
Rust безопаснее Go?
Оба безопасны по памяти. Rust дополнительно исключает гонки данных при компиляции; в Go их находит детектор гонок в тестах.
Можно ли использовать Go и Rust вместе?
Да, отдельными сервисами, которые общаются по сети, — самый частый и простой способ.
Кому больше платят?
Специалисты по обоим хорошо оплачиваются; разработчиков на Rust меньше, поэтому их сложнее найти.
Есть ли в Go дженерики?
Да, с Go 1.18 (2022). В Rust дженерики и трейты были с самого начала.
Форма
Сервисы
на Go
Пишу сервисы на Go там, где важны скорость отклика и нагрузка. Если задаче действительно нужен Rust — скажу честно и помогу найти правильное решение. Расскажите о проекте — отвечу в течение рабочего дня.