Программирование, да и вообще IT в целом — это всегда про компромиссы. Выигрывая в одном, мы обязательно проигрываем в другом. Не существует той самой «серебряной пули», то есть инструмента, решающего абсолютно все задачи. Точно так же нет и идеального языка программирования. Но что, если я скажу вам, что существует почти идеальный язык? Это Rust. Далее я постараюсь обосновать свои доводы, чтобы не показаться слепым крабом фанатом очередной хайповой технологии.

За почти 14 лет в IT я успел профессионально поработать с несколькими языками программирования: PHP, Ruby, JavaScript/TypeScript. Также мне довелось «потрогать» Python, Go, и даже Clojure и Haskell. Каждый из них имеет свои недостатки: у PHP — неудачная архитектура, у Ruby — медленная производительность, а у функциональных языков — нехватка библиотек и обучающих материалов. При разработке на этих языках приходится идти на уступки, выбирая между изящным синтаксисом, скоростью работы и необходимостью изобретать велосипеды на каждый чих. Однако с Rust ситуация совсем иная, и вот почему.
Borrow checker: боль, которая наконец-то имеет смысл
Контроллер заимствования (borrow checker) — это уникальная фича Rust, которая значительно поднимает кривую обучения и отпугивает новичков. Однако за сложной концепцией скрывается простая идея: данные в программе не могут «висеть в воздухе», они всегда должны иметь владельца. Довольно очевидно, что элемент массива, как и любой элемент коллекции, принадлежит этой коллекции, ведь он в ней хранится. Переменные, объявленные внутри функции, принадлежат области видимости этой функции. Звучит вполне логично, не правда ли? Это и есть владение.
fn main() {
let s = String::from("hello");
make_some_processing(s);
// Не скомпилируется, так как функция make_some_processing
// забрала владение над s, и здесь s уже недоступен
println!("Processed: {s}");
}
fn make_some_processing(some_string: String) {
println!("{some_string}");
}
Данные хранятся в памяти и к ним можно обратиться по какому-то адресу, в терминологии Rust — получить ссылку. Чтобы эти данные оставались согласованными, а во время выполнения программы не возникало нежелательных эффектов, можно сколько угодно данные читать, но изменять — только один. Иными словами, Rust позволяет взять у переменной (области памяти) множество ссылок на чтение или только одну — на запись. Это правило распространяется и на вложенные элементы коллекций и структур. Это и есть довольно упрощённое понимание заимствования.
fn main() {
let mut v = vec![1, 2, 3];
let a = &v;
let b = &mut v; // ошибка: нельзя иметь & и &mut одновременно
println!("{:?}", a);
}
Ссылки на несуществующие данные в понимании Rust лишены смысла (например, ссылка на элементы уже удалённого массива). Компилятор языка просто не допускает такие ситуации (привет тебе, undefined behaviour в «крестах»). Это и есть суть концепции времени жизни ссылок, или лайфтаймов.
// не скомпилируется, ибо к выходу из функции my_string будет уничтожен
// требуется явное указание времени жизни
fn get_str() -> &str {
let my_string = String::from("Hello");
my_string.as_str()
}
Таким образом, контроллер заимствования (borrow checker), отслеживая владение, заимствование и время жизни ссылок, позволяет писать безопасный, в том числе многопоточный код и устраняет необходимость в сборщике мусора. В Rust память освобождается в полуавтоматическом режиме: при завершении блока кода (области видимости) все переменные уничтожаются, если их владение не было явно передано.

Эта часть языка Rust буквально заставляет новичков страдать, плакать и просыпаться среди ночи в холодном поту. Однако это действительно боль во благо: это намеренное и продуманное архитектурное решение, а не прилипший к языку костыль, потому что «так исторически сложилось» (здравствуй, typeof null === ‘object’ в JavaScript). Компилятор «бьёт по рукам», не пропуская в код программы явные ошибки, вызывающие проблемы с памятью. Borrow checker — это не враг, а тот самый идеальный тимлид, который действительно учит и развивает разработчиков.
Cargo: если вы устали бороться с инструментом и хотите просто им пользоваться
Сколько копий было сломано об установку зависимостей и сборку проектов в C++ и C... Отсутствие единого реестра зависимостей и удобного инструментария превращают разработку на этих сложных языках в настоящий кошмар. К сожалению, в более высокоуровневых языках программирования ситуация не намного лучше. Разработчики на Python до сих пор не могут понять, какой менеджер зависимостей и линтер им использовать в 2025—2026 гг. JS-разработчики тоже до конца не определились: NPM, YARN или всё же PNPM?
Но в Rust есть (и существовал с самого начала проекта) замечательный Cargo — единый инфраструктурный инструмент для всего, что связано с разработкой на Rust. Разве это не прекрасно?
$ cargo new # инициализируем новый проект
$ cargo add reqwest # добавляем в проект зависимость
$ cargo build # скомпилировать проект
$ cargo run # запуск проекта или компиляция и запуск проекта
$ cargo fmt # форматирование кода проекта
$ cargo clippy # статический анализ, "линтинг" проекта
$ cargo test # запуск тестов
# Также можно отдельно установить:
$ cargo audit # аудит зависимостей на известные уязвимости
$ cargo nextest run # запуск тестов во множестве потоков для ускорения
Здесь и далее «крабы» — это Rust-разработчики. Это связано с «талисманом» языка: красным крабом.
Все «крабы», видя это, радостно щёлкают клешнями и, шагая боком, ликуют. Ведь теперь они навсегда избавлены от кошмаров: больше не нужно мучиться со сборкой зависимостей в «крестовых» проектах, переезжать с Flake8 на Ruff (или с Poetry на UV), разбираться с пересекающимися зависимостями в Maven и т.д.

Cargo — это зрелый инструмент, благодаря которому ВСЁ ПРОСТО РАБОТАЕТ «из коробки». В то время как экосистемы других языков программирования шли к этому последние 10—15 лет, но до сих пор не достигли такого уровня. Можно подискутировать, например, насчёт того же UV (написанного на Rust), но это отдельная зависимость, не связанная напрямую с Python.
Скорость выполнения впечатлит даже Доминика Торетто
Скорость выполнения Rust-кода действительно впечатляет. Из десяти самых высокопроизводительных веб-фреймворков семь написаны на Rust. В чём же секрет такой высокой производительности?

Во-первых, в компиляции напрямую в машинные инструкции без накладных расходов на runtime. Благодаря Borrow checker сборка мусора становится ненужной, а значит и тяжёлый рантайм, свойственный тому же Go и особенно языкам, работающим под JVM (Java, Kotlin, Scala, Clojure). К тому же LLVM, на основе которого построен компилятор Rust, применяет множество дополнительных оптимизаций при генерации машинного кода.
Во-вторых, абстракции с нулевой стоимостью (zero cost abstactions), как и в C++, позволяют создавать высокопроизводительный машинный код на основе относительно высокоуровневого rust-кода.
// LLVM при компиляции оптимизирует эту цепочку итераторов в простой цикл
fn main() {
let nums = vec![1, 2, 3, 4];
let sum: i32 = nums.iter().map(|x| x * 2).filter(|x| x > &4).sum();
println!("{:?}", sum);
}
В третьих, среды исполнения, предназначенные для конкурентного кода, такие как Tokio, позволяют максимально эффективно использовать «железо». Важно отметить что скорость выполнения может быть ограничена не только пределами процессора и пропускной способностью ввода-вывода, но и квалификацией конечного разработчика. Да, Rust обеспечивает безопасность многопоточного кода, но не гарантирует его эффективность.
Совсем отчаянные «крабы» могут пойти ещё дальше, например, в такие экспериментальные возможности, как использование SIMD-инструкций или вычисления на видеокартах.
Ничего подобного в Python, Ruby, NodeJS и PHP нельзя даже представить. Go, а также JVM-языки, несмотря на свою скорость, всё же сталкиваются с накладными расходами, связанными с их средой выполнения.
Enum-ы и pattern matching
Одной из ключевых особенностей системы типов Rust является поддержка алгебраических типов данных (ADT). Это означает, что, помимо структур (struct), поддерживаются типы-перечисления (enum). Структура должна обязательно содержать значения всех полей, а перечисление — указывать на одно из возможных значений.
struct Vehicle {
model_name: String, // марка автомобиля
engine_volume: usize, // объём двигателя
wheels_size: usize, // диаметр колёсных дисков
tear_type: TearType // тип установленных шин
}
enum TearType {
Summer, // летние шины
Winter, // зимние шины
Semiseason // всесезонные шины
}
Комбинируя структуры и перечисления, Rust позволяет описывать довольно сложные данные. Хотя возможность описания любых структур данных есть во многих языках программирования, но не все они позволяют строить логику на основе содержимого этих структур. Конечно, можно по коду развешивать нечитаемую лапшу из вложенных операторов if в связке с несколькими логическими И/ИЛИ (что часто встречается в Go, JavaScript, PHP и Java). Но языки с продуманной архитектурой предоставляют сопоставление по шаблону (Pattern Matching), и это является одной из сильных сторон Rust. Будет нечестно не сказать о том, что сопоставление по шаблону также доступно в Ruby, Python, Kotlin и C#. Это довольно объёмная и мощная функциональность языка, подробное описание которой может потянуть даже на серию статей. Здесь же мы рассмотрим несколько примеров:
let code = 404;
// сопоставление нескольких значений в одном ветвлении
match code {
200 | 201 | 202 => println!("Успех"),
400 | 404 => println!("Клиентская ошибка"),
500..=599 => println!("Ошибка сервера"),
_ => println!("Неизвестный код"),
}
enum State {
Start,
InProgress,
Done,
}
let s = State::InProgress;
// сопоставление элементов перечислений
match s {
State::Start => println!("Начали"),
State::InProgress => println!("В процессе"),
State::Done => println!("Готово"),
}
let x = Some(10);
// можно даже добавлять условия в ветвления при сопоставлении
match x {
Some(v) if v > 5 => println!("Больше пяти: {v}"),
Some(v) => println!("Значение: {v}"),
None => println!("Нет значения"),
}
В случае простых структур удобно использовать синтаксический сахар, который предоставляет язык, вроде конструкций if let и while let:
let value = Some(42);
if let Some(v) = value {
// здесь значение уже будет "развёрнуто" из монады
println!("Получили значение: {v}");
}
let mut stack = vec![1, 2, 3];
while let Some(item) = stack.pop() {
// аналогично работает для циклов. Если будет None, то проход остановится
println!("Сняли: {item}");
}
Обычный if/else, конечно, тоже присутствует, но мощные возможности сопоставления по шаблону позволяют описывать на Rust логику действительно больших и сложных приложений.
Обработка ошибок: глоток свежего воздуха
Ошибки в программах неизбежны и они требуют обработки. Большинство языков использует механизм исключений, который, несмотря на повсеместное применение, имеет существенные недостатки.
Один из них — неявный поток выполнения кода при пробросе ошибки, если она не была обработана сразу. Java, например, частично решает эту проблему, требуя либо обработкиChecked Exceptions, либо их добавления в сигнатуры методов. Но языки вроде Python позволяют игнорировать исключения, и это прекрасная возможность «выстрелить себе в ногу».
def read_config(path):
try:
return open(path).read()
except Exception:
return None # привет, загадочный None в рантайме
Возможно, причиной написания такого «кода с душком» будет низкая квалификация или безответственность разработчика, но сам язык предоставляет такую возможность.
В языках Go и Rust любая возникающая ошибка обязана быть обработана. В Rust для этого используется концепция монады Result, аналогичная той, что применяется в функциональных языках, таким как Haskell. Суть проста: операция, которая может завершиться ошибкой, возвращает либо успешный результат, либо ошибку (спасибо тебе, капитан Очевидность, ну или старший лейтенант Тавтология). Перечисление (enum), содержащее одно из этих двух состояний (какое-то значение Ok или ошибка Err), и есть результат выполнения операции.
В привычных нам языках программирования операции, которые могут быть завершены ошибкой, либо возвращают значение, либо бросают исключение. В Rust такие операции всегда возвращают значение. Это позволяет «крабам» обрабатывать ошибки именно там, где это действительно необходимо. Можно намеренно проигнорировать ошибочное состояние и вызвать панику, но случайно пропустить какой-либо Err невозможно.
fn user_settings_from_file() -> Result<String, io::Error> {
let settings_file_result = File::open("settings.ini");
let mut settings_file = match settings_file_result {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut settings = String::new();
match settings_file.read_to_string(&mut settings) {
Ok(_) => Ok(settings),
Err(e) => Err(e),
}
}
Использование Pattern Matching «в лоб», как показано выше, требует довольно много шаблонного кода. Из коробки Rust предоставляет синтаксический сахар для обработки ошибок (оператор ?), а также возможность разворачивать монаду Result напрямую в значение (методы unwrap, expect, map_or и т.д.).
fn user_settings_from_file() -> Result<String, io::Error> {
let mut settings_file = File::open("settings.ini")?;
let mut settings = String::new();
settings_file.read_to_string(&mut settings)?;
Ok(settings)
}
Также в экосистеме есть популярные удобные сторонние библиотеки, ставшие де-факто стандартом, такие как anyhow и thiserror.
use anyhow::Result;
use std::fs::File;
use std::io::{Read};
fn user_settings_from_file() -> Result<String> {
let mut settings_file = File::open("settings.ini")?;
let mut settings = String::new();
settings_file.read_to_string(&mut settings)?;
Ok(settings)
}
Все ошибки, которые могут быть обработаны на этапе компиляции, будут обработаны, исключив их попадание в промышленную эксплуатацию.
Null safety: когда NullPointerException остался где-то в параллельной вселенной
Нулевые указатели, значения, которые больше не существуют к моменту использования, и явные null-ы могут вызывать массу проблем в работе компьютерных программ. Даже создатель древнего языка программирования ALGOL Тони Хоар спустя годы признал null reference ошибкой на миллиард долларов. В целом мы привыкли к этому, но в функциональных языках, таких как Haskell и OCaml, подход иной. Если в типе указано, что это, допустим, строка, то там лежит только строка, а не пустое значение. Если же вычисление может вернуть как значение, так и его отсутствие, это явно указываетсяв виде перечисления (enum). Rust использует эту концепцию для обеспечения null safety (безопасности работы с пустыми значениями) на уровне системы типов (монада Option). В коде программист должен явно обработать как отсутствие значения (None), так и его наличие Some(value). Прощай, такой Java-кошмар как:
String s = null;
s.length(); // NullPointerException
Компилятор Rust даже не даст присвоить строковой переменной ничего, кроме строки. Здравствуй, прелесть:
use std::collections::HashMap;
let numbers = vec![10, 20, 30];
let x = numbers.get(1); // → Option<&i32>
let y = numbers.get(99); // → None
let mut map = HashMap::new();
map.insert("a", 1);
let v = map.get("a"); // → Some(&1)
let n = map.get("b"); // → None
let mut iter = [1, 2].iter();
let a = iter.next(); // → Some(&1)
let b = iter.next(); // → Some(&2)
let c = iter.next(); // → None
Система типов не даст случайно пропустить пустое значение. Компилятор требует явной обработки None и Some либо при помощи сопоставления по шаблону, либо операторами if let / while let, либо различными методами развёртки (unwrap, expect и прочие). Эта часть архитектуры языка позволяет разработчику писать безопасный код, не думая о том, что где-то внезапно проскочит отсутствующее значение и программа упадёт с ошибкой.
Всегда актуальная документация и тесты
Для большинства проектов, за исключением, возможно, самых популярных open-source решений, актуальность документации является насущной проблемой. Бизнес часто не выделяет на это ресурсы, технических писателей в компаниях мало, а сами разработчики нередко пренебрегают документированием. В итоге, чтобы разобраться в работе системы, приходится погружаться в исходный код и тесты, которые тоже не всегда написаны лаконично и структурированно. Благо к счастью, появиление LLM частично снимает эту боль.
Уверен, что сегодня ни один профессиональный разработчик не сомневается в необходимости написания тестов. Концепция пирамиды тестирования настоятельно рекомендует создавать множество простых и быстрых модульных тестов. Они позволяют покрывать бизнес-логику приложения в отрыве от инфраструктурных особенностей. Разработчики, использующие другие языки, сталкиваются с рядом вопросов, которые необходимо решить до написания тестов:
Какой фреймворк тестирования использовать?
Где размещать файлы тестов?
Стоит ли тестировать приватные методы и как?
Rust предлагает своё изящное решение двух описанных проблем. Если код по определению всегда отражает актуальное состояние программы, то почему бы не держать его вместе с документацией и тестами? В Rust исходный код в одном файле включает комментарии, на основе которых генерируется документация, а также набор тестов. Это позволяет следить за актуальностью документации и тестировать также закрытые (приватные) методы структур.
/// Три слэша объявляют блок doc comments, на его основе будет сгененрирована
/// документация. *Markdown* также поддерживается.
/**
Сигнатуры аргументов и возвращаемых значений будут выведены из исходного кода
*/
pub fn strange_add(left: u64, right: u64) -> u64 {
let random_number = get_random_number();
left + right + random_number
}
// Обратите внимание, что это "приватная фукнция"
fn get_random_number() -> u64 {
42
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = strange_add(2, 2);
assert_eq!(result, 46);
}
// Мы можем даже написать тест на приватную функцию
#[test]
fn test_random() {
let random = get_random_number();
assert_eq!(random, 42);
}
}
Тесты также запускаются при помощи Cargo:
$ cargo test

Документация будет сгенерирована при помощи команды:
$ cargo doc --open # документация будет сгенерирована и открыта в браузере

А если совместить оба подхода? Так тоже можно. И это даёт гарантированно актуальные примеры использования.
/// Три слэша объявляют блок doc comments, на его основе будет сгененрирована
/// документация. *Markdown* также поддерживается.
/**
Сигнатуры аргументов и возвращаемых значений будут выведены из исходного кода
```rust
let result = some_lib::strange_add(5, 10);
assert_eq!(result, 57);
```
Любой rust-код в doc comments будет рассмотрен как тест (doc tests)
и будет запущен при выполнении тестов через cargo test
*/
pub fn strange_add(left: u64, right: u64) -> u64 {
let random_number = get_random_number();
left + right + random_number
}
// Обратите внимание, что это "приватная фукнция"
fn get_random_number() -> u64 {
42
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = strange_add(2, 2);
assert_eq!(result, 46);
}
// Мы можем даже написать тест на приватную функцию
#[test]
fn test_random() {
let random = get_random_number();
assert_eq!(random, 42);
}
}
Вся документация в Rust-сообществе генерируется именно таким образом. Не нужно возиться с тем же Docusaurus и тратить время на написание (или копипаст) отдельных markdown-файлов. Просто удивительно, насколько это удобно.
Богатая экосистема
Многие языки программирования, предназначенные для массового применения в продуктивной среде и корпоративной разработке, испытывают недостаток сторонних библиотек и ресурсов для изучения. В Rust с этим всё хорошо. На момент написания статьи хранилище сторонних библиотек (сайт https:://crates.io) содержит почти 207 тыс. пакетов. Конечно, не все они заслуживают добавления в проект, но нужный крейт (сторонняя библиотека Rust) найдётся «на все случаи жизни». В таблице ниже привен небольшой список качественных библиотек, которые лично я рекомендую:
Библиотека (crate) |
Назначение и краткое описание |
|---|---|
Высокопроизводительная библиотека для работы с реляционными базами данных |
|
Популярный асинхронный рантайм для высоконагруженных приложений |
|
Мощный HTTP-клиент для Rust |
|
Популярный ORM для Rust |
|
Высокопроизводительный web HTTP-фреймворк |
|
Конкурент axum, высокопроизводительный web HTTP-фреймворк |
|
Библиотека сериализации и десериализации структур |
|
Де-факто стандарт для обработки ошибок в клиентских бизнес-приложениях |
|
Стандарт для идиоматической обработки ошибок при создании своих библиотек |
|
Удобный парсер аргументов командной строки |
На сайте docs.rs также можно найти актуальную документацию по всем публично доступным крейтам, которая генерируется описанным выше способом (cargo doc). Там же есть ссылки на официальную документацию Rust и его стандартной библиотеки. Кроме того, существует рассылка Rust Weekly, где можно получить актуальные новости об экосистеме языка, информацию о нововведениях, а также ссылки на блоги с качественными обучающими материалами.
Конкурентный код
Написание программ, работающих в нескольких потоках, всегда было головной болью для программистов. Конечно, эта техника значительно ускоряет работу программ, но в то же время часто вносит в код трудноуловимые ошибки, связанные с гонками данных и взаимными блокировками (deadlocks). Гонка данных (data race) — состояние, когда несколько потоков обращаются к одной и той же области памяти и изменяют её содержимое на своё усмотрение. Взаимная блокировка — это состояние, когда один поток ждёт результата выполнения другого потока, а другой поток, соответственно, ждёт результаты выполнения первого потока. Они оба ожидают и не могут продолжить работу.
Такие ошибки чрезвычайно сложно диагностировать, и они могут привести к повреждению пользовательских данных и, как следствие, к материальным потерям для бизнеса.
Однако контроллер заимствования (borrow checker) кардинально меняет ситуацию. Благодаря ему (и, конечно, примитивам синхронизации, а также реализации типажей Send и Sync) Rust позволяет писать безопасный многопоточный код и забыть о гонках данных. Компилятор просто не пропустит такие места в коде, которые могут вызвать data race. Эта возможность языка называется fearless concurrency.
Как правило, в Rust выделяют три способа написания конкурентного кода:
Вилочное распараллеливание
Каналы
Разделяемое изменяемое состояние
Вилочное распараллеливание идеально подходит для ситуаций, когда имеется несколько абсолютно независимых задач, которые хотелось бы выполнить параллельно.
use std::thread;
fn main() {
let nums1 = (1..=5_000_000).collect::<Vec<_>>();
let nums2 = (5_000_001..=10_000_000).collect::<Vec<_>>();
// fork
let t1 = thread::spawn(move || nums1.iter().sum::<u64>());
let t2 = thread::spawn(move || nums2.iter().sum::<u64>());
// join
let s1 = t1.join().unwrap();
let s2 = t2.join().unwrap();
println!("sum = {}", s1 + s2); // выведет sum = 50000005000000
}
В реальных проектах std::thread, как правило, не используют. Чаше разработчики выбирают крейт rayon. Он «из коробки» предоставляет такие возможности, как пул потоков и балансировку нагрузки. Стоит упомянуть, что rayon подходит для cpu-bound задач. Как быть с io-bound, я расскажу далее.
Каналы в Rust работают схоже с Go. Они представляют собой потокобезопасную очередь, являясь некой «односторонней трубой» для передачи данных от одного потока к другому. Каналы идеально подходят для сценариев, где требуется передавать данные или события между независимыми потоками (подзадачами) и разделять ответственность. Например, один поток производит какое-то тяжёлое вычисление и иногда отдаёт прогресс основному потоку:
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
enum Progress {
Step(u32),
Done,
}
fn main() {
let (tx, rx) = mpsc::channel::<Progress>();
thread::spawn(move || {
for i in 1..=10 {
thread::sleep(Duration::from_millis(100));
tx.send(Progress::Step(i)).unwrap();
}
tx.send(Progress::Done).unwrap();
});
for msg in rx {
match msg {
Progress::Step(i) => println!("progress: {i}/10"),
Progress::Done => {
println!("done!");
break;
}
}
}
}
Разделяемое изменяемое состояние — это классические мьютексы, которые работают аналогично многим другим языкам программирования. Их уместно применять, когда в приложении есть один объект, доступ к которому имеют несколько потоков (общий счётчик, кеш в памяти и т.д.). Разделяемое изменяемое состояние лучше не использовать, когда есть тяжёлые вычисления или долгие операции ввода-вывода, поскольку есть риск, что поток будет долго удерживать блокировку, что негативно скажется на общей производительности приложения.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0u64));
let mut handles = Vec::new();
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
for _ in 0..100_000 {
// Берём блокировку
let mut num = counter.lock().unwrap();
// Меняем разделяемое состояние
*num += 1;
// Блокировка освободится, когда num выйдет из scope
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
// Выведет: Результат: 1000000
println!("Результат: {}", *counter.lock().unwrap());
}
Rust сам по себе не сделает из вас гуру многопоточного программирования. Он просто не позволит совершить ошибки, которые в других языках считаются нормой. И, да — deadlock всё равно можно поймать. Но вы хотя бы будете точно знать, что это ваша вина, а не недоработка языка.
Асинхронный код
Асинхронность в Rust хоть и является незавершённой частью языка, но не «костылём»: асинхронный Rust очень производителен и надёжен. Идея в том, что стандартная библиотека предоставляет только примитив для асинхронных вычислений (Future, async/await). Future — это ленивое вычисление, которое само по себе ничего не делает, пока его кто-то как-то не опрашивает (polling). В отличие от Promise в JavaScript оно не запускается автоматически. Это лишь некая структура в памяти, реализующая типаж. Для опроса Future требуется среда исполнения (runtime). По факту, стандартом индустрии является tokio, под ним работает, пожалуй, большая часть веб-приложений на Rust.
use tokio::time::{sleep, Duration};
#[tokio::main] // это макрос, он поднимает асинхронный рантайм
async fn main() {
let h1 = tokio::spawn(async {
sleep(Duration::from_secs(1)).await;
println!("Hello from task 1");
});
let h2 = tokio::spawn(async {
println!("Hello from task 2");
});
h1.await.unwrap();
h2.await.unwrap();
}
Тут есть один неприятный момент: типы могут быть очень сложными, и борьба с borrow checker-ом может стать настоящим испытанием. Но tokio создаёт легковесные потоки, позволяя раскидать их по разным тредам и выжать максимум производительности. Это не один поток с event loop в JavaScript и не GIL в Python. Здесь речь идёт про действительно высокопроизводительные приложения.
Всё ли так сладко? Капелька дёгтя во имя справедливости
Было бы несправедливо умолчать о недостатках Rust. Первое, что приходит на ум, — это высокий порог входа. Концепция владения/заимствования и времени жизни очень напрягают мозг. Кроме того, компиляция, особенно в крупных проектах, происходит медленно, ибо rustc (компилятор языка) проделывает колоссальный объём вычислений.
Асинхроннроый код писать сложно. Сейчас хоть и стало проще, но до интуитивности JavaScript ещё очень далеко. Отсутствие reflection, хоть это способствует производительности, усложняет такие задачи, как DI (к слову, адекватного DI не существует). Многословность, например отсутствие неявных преобразований типов переменных, быстро утомляет. А unsafe-код — это просто больно.
Конечно, «серебряных пуль» в IT не бывает — все строится вокруг компромиссов. Но в случае с Rust эти компромиссы минимальны.
Это не очередная «перспективная технология будущего», Rust уже здесь
Лет десять назад ещё можно было сказать: «Rust — это что-то для нердов/хипстеров/{place subculture name here}». Но сегодня это взрослый, мощный, активно развивающийся и вездесущий язык программирования. Очевидно, что та же JetBrains не стала бы создавать отдельную IDE под очередную игрушку. Массовые мемные переписывания на Rust GNU-утилит тоже остались в прошлом. Сегодня на этом языке создают как нишевые, так и повсеместно используемые большие проекты. Стоит упомянуть следующее:
Замечательный текстовый редактор Zed
Серверную среду исполнения JavaScript Deno
Векторную базу данных Qdrant
Некоторые части библиотек операционных систем, причём как
Windows, так иLinuxКриптаны уже давно приняли Rust и тот же etherium-клиент parity
Замечательные эмуляторы терминала, такие как Alacritty и Wezterm
Часть экосистемы мейнстримных языков (SWC в JavaScript, uv в Python)
Этот список можно долго продолжать. Помимо перечисленных областей применения, на Rust пишут WebAssebly-приложения, десктопные программы и даже софт для встраиваемых систем. Он повсюду!
Так что, друзья, откладываем в сторону JavaScript/Python/Go/Java-суету, заходим в терминал и пишем:
$ cargo new my_awesome_project

Приятного полёта!
Комментарии (6)

kipar
04.12.2025 08:04Borrow checker — это не враг, а тот самый идеальный тимлид, который действительно учит и развивает разработчиков.
угу, например если вы хотите две мутабельные ссылки на два разных элемента массива, то он научит что так делать нельзя и разовьет ваши знания стандартной библиотеки - split_at_mut, get_disjoint_mut, iter_mut. Заодно и пакетным менеджером научит пользоваться, ведь если вы хотите взять больше двух ссылок то вам порекомендуют использовать специально написанный для этого crate - https://github.com/mcmah309/indices
Вот только нужно ли вам это развитие?
evgeniyrru Автор
04.12.2025 08:04А нужно ли пытаться обойти borrow checker? Зачем из одного языка (Rust) делать другой (Кресты)?

ruslaniv
04.12.2025 08:04Меня в расте всегда немного озадачивала некая "мемность" языка. Довольно часто в профильных комьюнити вижу некий легкий, саркастичный троллинг / стеб раста, смысл которого сводится к тому, что апологеты считают его решением всех бед в CS, а остальные видят его как пятое колесо в телеге. А вот подобных мемов про тот же го по моему вообще ни разу не видел.
При этом как язык он же действительно достаточно крут и в плане реализации и в плане производительности.
black_warlock_iv
Линейных типов немножко не хватает. А в целом согласен.