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

I'm a rich-a$$ b$tch
I'm a rich-a$$ b$tch

За почти 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, // диаметр колёсных дисков
	  tire_type: TireType // тип установленных шин
}

enum TireType {
	  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)

Назначение и краткое описание

sqlx

Высокопроизводительная библиотека для работы с реляционными базами данных

tokio

Популярный асинхронный рантайм для высоконагруженных приложений

reqwest

Мощный HTTP-клиент для Rust

diesel

Популярный ORM для Rust

axum

Высокопроизводительный web HTTP-фреймворк

actix

Конкурент axum, высокопроизводительный web HTTP-фреймворк

serde

Библиотека сериализации и десериализации структур

anyhow

Де-факто стандарт для обработки ошибок в клиентских бизнес-приложениях

thiserror

Стандарт для идиоматической обработки ошибок при создании своих библиотек

clap

Удобный парсер аргументов командной строки

На сайте 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
Мы начинаем забывать, кто мы такие. Исследователи и первооткрыватели, а не землеройки
Мы начинаем забывать, кто мы такие. Исследователи и первооткрыватели, а не землеройки

Приятного полёта!

Комментарии (108)


  1. black_warlock_iv
    04.12.2025 08:04

    Линейных типов немножко не хватает. А в целом согласен.


  1. kipar
    04.12.2025 08:04

    Borrow checker — это не враг, а тот самый идеальный тимлид, который действительно учит и развивает разработчиков.

    угу, например если вы хотите две мутабельные ссылки на два разных элемента массива, то он научит что так делать нельзя и разовьет ваши знания стандартной библиотеки - split_at_mut, get_disjoint_mut, iter_mut. Заодно и пакетным менеджером научит пользоваться, ведь если вы хотите взять больше двух ссылок то вам порекомендуют использовать специально написанный для этого crate - https://github.com/mcmah309/indices
    Вот только нужно ли вам это развитие?


    1. evgeniyrru Автор
      04.12.2025 08:04

      А нужно ли пытаться обойти borrow checker? Зачем из одного языка (Rust) делать другой (Кресты)?


      1. RedWolf
        04.12.2025 08:04

        Простите, а с каких это пор плюсы стали крестами?


        1. evgeniyrru Автор
          04.12.2025 08:04

          Always has been


          1. RedWolf
            04.12.2025 08:04

            При слове Кресты возникает только одно в голове - знаменитое питерское СИЗО. Как раз два ++.


      1. kipar
        04.12.2025 08:04

        превращать - не нужно. Просто я слышал разные версии:

        • borrow checker необходимое зло

        • я научился с ним бороться и почти всегда побеждаю

        • мне пофиг на перформанс и я просто обмазываю всё Ref<Cell

        • я использую ECS/алгоритмы на массивах как раз чтобы не встречаться с ним, теперь я использую индексы вместо ссылок и могу мутировать что хочу.

        но называть его учителем и тимлидом - это что-то новое. Ну и что этот тимлид говорит когда я хочу взять мутабельные ссылки на разные элементы массива?


  1. ruslaniv
    04.12.2025 08:04

    Меня в расте всегда немного озадачивала некая "мемность" языка. Довольно часто в профильных комьюнити вижу некий легкий, саркастичный троллинг / стеб раста, смысл которого сводится к тому, что апологеты считают его решением всех бед в CS, а остальные видят его как пятое колесо в телеге. А вот подобных мемов про тот же го по моему вообще ни разу не видел.

    При этом как язык он же действительно достаточно крут и в плане реализации и в плане производительности.


    1. evgeniyrru Автор
      04.12.2025 08:04

      Может быть мемасики говорят об интересе? Люди обычно не шутят про то, что не интересно


    1. kipar
      04.12.2025 08:04

      Го - это же тот язык, где 50% кода — конструкция
      if err != nil {return err} ?


      1. cupraer
        04.12.2025 08:04

        Го — это тот язык, про который его автор сказал, что он специально разработан, чтобы «дебилы, которых понабрали в гугл по объявлению, могли приносить хоть какую-то пользу, но не поломали все остальное вокруг».


        1. V1tol
          04.12.2025 08:04

          Го - язык, разработанный на коленке в довесок к разработанному garbage collector. Засунуть такое количество мест, где можно себе отстрелить ногу как в крестах, в язык "для дебилов" - это было гениальное решение. Моё любимое - сделать close на канале в читающей горутине сразу же вызывает панику при следующей попытке записи в этот канал - приходится прыгать вокруг и обмазываться дополнительными чтениями, каналами или AtomicBool. В расте тебе просто вернётся Result::Err, который спокойно обрабатываешь или игнорируешь и идёшь дальше.


          1. cupraer
            04.12.2025 08:04

            Это к Пайку, все его решения были гениальны, но не все раскручивал Г, поэтому остальные — умерли, не родившись.


        1. sdramare
          04.12.2025 08:04

          Он никогда этого не говорил, тебя уже много раз на хабре ловили на этом вранье, а все ты не успокоишься.


          1. cupraer
            04.12.2025 08:04

            Меня ловили на вранье?! Мальчик, иди обратно в школу, и не высовывай нос, пока не поумнеешь.

            https://www.youtube.com/watch?v=uwajp0g-bY4 — это видео, на котором он это говорит
            ▸ «the key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt» — это текст, если в гугле забанили.


            1. sdramare
              04.12.2025 08:04

              И где здесь про "дебилов"? Где "набрали по объявлению"(это видимо ты свой опыт проецируешь). Где про "поломали все остальное вокруг?" и "делали хоть что то"? Я понимаю, что ты не знаешь английский, по-этому вот тебе перевод

              наши программисты — это сотрудники Google, а не исследователи. Обычно они довольно молоды, только что окончили учёбу, вероятно изучали Java, возможно C или C++, возможно также Python. Они не в состоянии разобраться в очень изощрённом языке, но мы хотим, чтобы они создавали хорошее программное обеспечение. Поэтому язык, который мы им даём, должен быть для них простым для понимания и лёгким в освоении.

              Как видишь, это "несколько" отличается от твоего вранья

              Пайк пишет про молодых и безусловно таланливых людей в начале их карьеры в гугле, которым го позволяет сразу показывать высокую продуктивность, не тратя кучу времени на глубокое изучение сематинтического сложного языке типа С++, а не про "дебилов по объявлению". Единственный дебил тут это ты.


              1. cupraer
                04.12.2025 08:04

                Плюсанул карму, такой запал не должен пропадать зря. Хоть посмеялся.


                1. APh
                  04.12.2025 08:04

                  Плюсанул карму

                  Можешь и мне плюсануть заодно. )) А то политически не нейтральные участники, в нарушение устава, все баллы посрезали.


                  1. cupraer
                    04.12.2025 08:04

                    Я не подаю по субботам.


                  1. RaptorTV
                    04.12.2025 08:04

                    Мне тоже (


              1. bogolt
                04.12.2025 08:04

                Имхо если читать между строк то именно это там и написано. Низкий уровень профессионализма, нежелание думать, требование выкатывать фичи породили вот такой подход - язык который минимизирует требования к программисту, позволяет обычному программисту написать поддерживаемй, быстрый а иногда еще и многопоточный код.


                1. sdramare
                  04.12.2025 08:04

                  Низкий уровень профессионализма породил язык, который позволяет писать быстрый и поддерживамый код? Чего? А какие языки порождаются тогда при высоком уровне профессионализма, которые не позволяют писать быстрый и поддерживамый код?


                  1. dayman092
                    04.12.2025 08:04

                    Те которые имеют противоположные признаки из списка, которых нет в Го:

                    -долгая компиляция(не поддерживаемый код)

                    -большой выбор операторов, перегрузка операторов, долго учить язык(нужен высокий уровень профессионализма)

                    -выключенный автоформаттер и линтер кода

                    -сложная система зависимостей или линковки

                    -выходной файл работает через интерпретатор(медленный код)

                    Убрать последний пункт и всё совпадет с Растом


                    1. sdramare
                      04.12.2025 08:04

                      Я просто не понимаю этого аргумента, "го это язык для низкого уровень профессионализма, потому что на нем можно писать быстрый, надежный и легко поддерживый код" и дескать это очень плохо. Мне то казалось что возможность писать эффективный и поддеживаемый код это по сути конечная цель любого языка.


  1. cupraer
    04.12.2025 08:04

    Каждый из них имеет свои недостатки: у PHP — неудачная архитектура, у Ruby — медленная производительность, а у функциональных языков — нехватка библиотек и обучающих материалов.

    Спасибо, поржал. Судя по тексту, раст вам известен примерно на таком же уровне, как вышеупомянутые языки.

    Из десяти самых высокопроизводительных веб-фреймворков семь написаны на Rust.

    А шопифай на рельсах, дальше что? И почему-то мне кажется, что один шопифай обслуживает больше трафика, чем все вместе взятые бизнесы на «семи самых производительных фреймворках».

    Асинхронность в Rust хоть и является незавершённой частью языка, но не «костылём»: асинхронный Rust очень производителен и надёжен. 

    Назовите хоть один сервис под хайлоадом на расте. Асинхронность в расте — несомненный костыль, который рассыпается (и будет рассыпаться всегда, by design) в кластере.

    Нулевые указатели, значения, которые больше не существуют к моменту использования, и явные null-ы могут вызывать массу проблем в работе компьютерных программ.

    Несомненно. А раст — единственный язык, победивший эту проблему? А существует хоть одно приложение, чуть сложнее калькулятора, в котором не используется ансейф?

    В общем, мне понятны ваши восторги, но они обязаны вашему ограниченному кругозору, а не качеству раста как такового. Акторная модель, нативные абстрактные синтаксические деревья, инфраструктура метапрограмирования (макросов), защита от сбоев — вот всего лишь несколько первых пришедших на ум важнейших атрибутов современного языка. А не синтетические тесты «скорости фреймворка» и не пакетные менеджеры, которые в 2025 есть везде, и среди которых cargo — далеко не самый адекватный.


    1. domix32
      04.12.2025 08:04

      Несомненно. А раст — единственный язык, победивший эту проблему? А существует хоть одно приложение, чуть сложнее калькулятора, в котором не используется ансейф?

      Кажется это принципиально невозможно, ибо общение с той же OS во многих случаях оборачивается вызовом сишных API - zed что-то там мутит с unix stream, uv - WinAPI дёргает, helix и zellij интегрируют плагины на wasm, которые могут быть на произвольном языке, что опять же - unsafe граница между языками. А так, чтобы оно никак не взаимодействовало с системой или другими языками и было сложнее калькулятора. Можно ли считать gleam продвинутым калькулятором или пойдёт под категорию?


      1. cupraer
        04.12.2025 08:04

        Я не о низком уровне, который реализован авторами языка и протестирован всеми, кто язык использует.

        При чем тут Gleam? Он работает на BEAM, что приносит всю кухню эрланга; добиваться отказоустойчивости можно не только запретом делать плохое (неправильный подход, которому следуют почти все) — но и буквально разрешением делать плохое с обещанием разрулить это плохое за разработчика.


        1. domix32
          04.12.2025 08:04

          Компилятор языка написан на Rust и весь рантайм к нему вроде тоже на нём - по ссылке репа, справа в сайдбаре с инфой можно посмотреть распределение языков. Считаем ли мы компилятор и рантайм языка продвинутым калькулятором или таки относим его к легитимным приложениям сложнее калькулятора?

          Я не о низком уровне, который реализован авторами языка и протестирован всеми, кто язык использует.

          Так и я не про unsafe в std, а ровно тот unsafe который появляется непосредственно в проекте некалькулятора.


          1. cupraer
            04.12.2025 08:04

            Компилятор языка написан на Rust и весь рантайм к нему вроде тоже на нём […]

            Компилятор и рантайм — довольно специальные типы приложений, написание которых плохо экстраполируются на пользовательские. Я, правда, не очень понимаю, какое это всё имеет отношение к теме дискуссии. Раст не безнадёжен, но вопрос о применимости этого языка для сложных приложений — пока не закрыт.


            1. domix32
              04.12.2025 08:04

              Ви так говорите, словно компилятор без пользователей живёт. Собственно про это и вопрос - считаем мы это как приложение проходящее обозначенный критерий или считаем, что это калькулятор, пусть и немного более замороченный. Что могло бы пройти этот критерий? Редакторы кода (zed, helix) не проходят, потому что у них есть unsafe, приложенька на каком-нибудь tauri/dioxus тоже, т.к. фреймворк содержит unsafe, но если мы считаем это допустимым в пределах библиотеки, но не собственного приложении, то внезапно критерий начнут проходить немало приложений.


              1. cupraer
                04.12.2025 08:04

                У меня может быть профдеформация, но я уже довольно давно не сталкиваюсь с задачами, которые на ¾ решены существующими библиотеками, поэтому с моей колокольни — мне придётся упарываться в ансейф самому, а значит — придётся сражаться с внутренним цензором, от чего чахнет аура и засоряется карбюратор.

                По гамбургскому счету, мы — разумеется — считаем это допустимым в пределах библиотеки, и, как я и сказал, раст небезнадежен (в отличие от D, например, который решает какую-то вообще непонятную для меня задачу в окружающем меня мире).

                P. S. Компилятор живет с очень лояльными пользователями. Я отправил кучу патчей во все без исклчения библиотеки, с которыми работаю на постоянной основе, и в корку языка, с которым работаю. Упёрся в польскую версию виндоуз — починил и завтра проблемы нет. С банковским приложением так не получится.

                P. P. S. Если подытожить то, что я имею в виду, одним предложением — мне глубоко претит идея запретить делать плохо (а потом, конечно, разрешить в стиле «если нельзя, но очень хочется — то можно»). Я фанат идеи разрешить делать плохо, Let It Crash, и продуманная защита от сбоев вместо запрета ошибаться.


                1. domix32
                  04.12.2025 08:04

                  Если подытожить то, что я имею в виду, одним предложением — мне глубоко претит идея запретить делать плохо

                  Собственно, для этого и существует возможность использовать иные языки. Кто-то не хочет ловить сегфолты и переполнения буфферов, кого-то устраивает и то и другое, но интересует скорость и простота разработки при большей производительности. Кого-то скорость вообще не волнует и тот пишет на каком-нибудь PHP вообще и радуется, что его ошибки заканчиваются на paamayim nekudotayim.


                  1. cupraer
                    04.12.2025 08:04

                    Не уверен, что мы говорим о одном и том же (и что мне удалось донести мысль). Все языки из примерно двух десятков, известных мне не понаслышке, — как раз запрещают делать плохо (кроме одного).

                    «У нас есть сегфолты, которые вас когда-нибудь обязательно ударят сзади по голове, просто избегайте их». «У нас есть рекурсия, просто помните, что хвостовой оптимизацией мы не озаботились, и карета Фибоначчи отыквится в полночь». «У нас есть die(), но вот вам триста фреймворков, чтобы жить вечно».

                    Армстронг однажды заявил: «программисты будут^W будем делать ошибки, мы не роботы, такова наша природа; поэтому надо просто уметь корректно восстановиться после непреднамеренной ошибки, а преднамеренные — сделать штатным управлением контроля». Так появились деревья супервизоров (которые потом криво и косо попытался архитектурно повторить кубер) и идеология Let It Crash.

                    Чтобы повалить виртуальную машину эрланга — нужно три бородатых многомудрых хакера и один Роберт Вирдинг, который знает, куда этим молотком ударить. Мы не обрабатываем ошибки, мы позволяем процессу упасть, остальное — за нас сделает виртуальная машина, если мы хоть чуть-чуть позаботились об архитектуре.

                    ——

                    За «paamayim nekudotayim» — огромное спасибо, мне раньше не попадалось.


                    1. sdramare
                      04.12.2025 08:04

                      Чтобы повалить виртуальную машину эрланга — нужно три бородатых многомудрых хакера и один Роберт Вирдинг

                      Или всего одного запроса который вызовет out of memory. Или очередного бага в GC


                    1. domix32
                      04.12.2025 08:04

                      мне раньше не попадалось

                      его в какой-то момент зарефакторили в языке, когда евреи перестали быть основными ментейнерами. Где-то в районе php 5 кажется


                      1. cupraer
                        04.12.2025 08:04

                        Я перестал писать на похапе еще до того, как Моисей обзавелся скрижалями.


    1. sdramare
      04.12.2025 08:04

      Назовите хоть один сервис под хайлоадом на расте

      Pingora в cloudflare, которая держит 40 млн запросов в секунду это достаточный хайлоад или еще нет? Ηypervisor в Azure workloads? Video encoding pipeline в Discord?


    1. DaniilSM
      04.12.2025 08:04

      Назову. Часть кода серверов Discord была переписана с Go на Rust. Гугл в помощь


      1. cupraer
        04.12.2025 08:04

        «Часть кода серверов»  — это не сервис. У меня часть кода серверов на баше написана.

        Вон выше действительно неплохой, хоть и очень нишевой пример — Pingora. И все равно, всё это пока выглядит как ассемблерные вставки в коде на си: вот тут автору почему-то втемяшилось написать малюсенький кусочек на другом языке. Почему?! Зачем? Откуда понятно, что тот же кусок на бейсике был бы хуже?


        1. domix32
          04.12.2025 08:04

          Не, там буквально история про сервис. Конретно - сервис уведомлений. В какой-то момент интенсивность реквестов на него превысила 1mrps и половина уведомлений перестала доходить, особенно если канал имел довольно большое количество участников - тогда под 100к человек каналы были ещё не настолько частым явлением. В итоге гошечка не справлялась с наплывом и дрочила память как полоумная туда-сюда из-за чего могла профукать вспышку и не послать уведомление. В итоге сервис переписали на Rust, чем подняли себе предел RPS, уменьшили потребление памяти к почти константному и получили стабильную доставку сообщений.


          1. cupraer
            04.12.2025 08:04

            Круто. Тогда беру свои слова обратно, надо чаще заглядывать к соседям, а то у них вон дети уже выросли давно. Пригляжусь повнимательнее тогда (разумеется, я пошел посмотреть, как там с акторной моделью, всем заправляет actix, вот его ради знакомства с языком я и попробую подвинуть с пьедестала :)

            Спасибо.


          1. cupraer
            04.12.2025 08:04

            Вашими мольбами, фактически: https://habr.com/ru/articles/973798/


  1. domix32
    04.12.2025 08:04

    Асинхронность в Rust хоть и является незавершённой частью языка, но не «костылём»:

    К сожалению - является именно костылём. Во-первых раскраска функций даёт несколько отличный синтаксис и инвалидирует работу borrow checker, позволяет писать честные асинхронные безопасные дедлоки.

    Вторая проблема, что синтаксический сахар для асинхронности на самом деле генерирует некоторый не самый хороший код с точки зрения архитектуры и расширять его больно, а в определённых ситуациях приходится ещё и костыли дописывать, чтобы асинк можно было использовать адекватно. Так что всё таки костыль.

    Отдельно стоит заметить, что некоторые дизайнерские решения относительно API стандартной библиотеки также вызывают вопросы, т.к. то что в Хаскелл было сделано для всех и сразу в Rust имеет несколько полу-копипастных частных имплементаций - всякие map на Option один из таких примеров.

    Всегда актуальная документация и тесты

    Это в общем-то не языковая проблема. Добавить генерацию документации не слишком сложно, а вот поддерживать её актуальность - проблема. Из принципиальных фишек cargo doc есть то что сниппеты кода в документации автоматически можно тестировать, да.

    Здесь и далее «крабы» — это Rust-разработчики. Это связано с «талисманом» языка: красным крабом.

    Это не совсем так. Маскотом Rust является cRUSTacean - то бишь ракооборазный. Так что не только в доте раки зимуют.


    1. cupraer
      04.12.2025 08:04

      Из принципиальных фишек cargo doc есть то что сниппеты кода в документации автоматически можно тестировать, да.

      Принципиальных? А ничё, что в питоне доктесты появились сто лет назад, а в эликсире, и, вслед за ним, в эрланге и том же глиме — десять лет назад?


      1. domix32
        04.12.2025 08:04

        Сравниваю с классическими компилируемыми языками в нишу которых воткнулся Rust, поэтому в сравнении с ними это принципиально новая фича. C, C++, C#, D - вроде никто из них до сих пор не поддеживает доктесты, но это не точно. В каком-нибудь JS с появлением node/npm тоже могли появиться, но в природе с ними не сталкивался. Аналогичная история с python - видел только примеры автоматической генерации, но не встречал автотестов в doc комментариях. Про эрланг ничего особо не знаю, ну а ржавый gleam начал появляться видимо с появлением Rust 1.0 те самые 10 лет назад - у кого доктесты были подсмотрены - вопрос открытый.


        1. cupraer
          04.12.2025 08:04

          Что такое «ржавый gleam»?


          1. domix32
            04.12.2025 08:04

            1. cupraer
              04.12.2025 08:04

              С этим языком я знаком, причем не совсем понаслышке.

              Он не имеет вообще никакого отношения к расту, кроме того, что Луис на хайпе решил писать транспайлер в эрланг на расте. Ровно с таким же успехом его можно было бы написать на похапе, на баше, или вообще на связке lexx/yacc. Исполняемый код вообще ничего общего с растом не имеет — это BEAM байткод, который, к тому же, создается нативным компилятором эрланга.


              1. domix32
                04.12.2025 08:04

                на хайпе решил писать транспайлер в эрланг на расте

                Собственно это я и зову ржавым кодом. Oxidation, если угодно. Результат призыва RIIR.

                Сам Gleam это язык, который превращается в BEAM байткод, закономерно совместимый с другим аналогичным байткодом, который можно генерировать на Эрланге. Кажется его можно использовать не имея эрланга в качестве необходимой зависимости - то есть оно не транспайлится в Erlang по-умолчанию.


                1. cupraer
                  04.12.2025 08:04

                  https://gleam.run/frequently-asked-questions/#what-does-gleam-compile-to

                  Когда я начал приносить правильные типы в BEAM, передо мной тоже стоял выбор языка, и я выбрал эрланг, чтобы не звать кучера с собой в постель: я создаю эрланговские формы и отдаю их компилятору без посредников. Да, медленнее. На этапе компиляции же, не в рантайме. А в рантайме я в теории быстрее глима, потому что правильно резолвлю типы, но это уже другая история.

                  Но Луис мне сильно помогал советами типа «по каким граблям ходить не нужно».


        1. cupraer
          04.12.2025 08:04

          1. domix32
            04.12.2025 08:04

            Несмотря на название, doctest не предназначен для написания документации и тестирования кода из примеров. Это просто хороший и быстрый фреймворк для тестирования, функционально аналогичный Google Test и Catch2. Для генерации документации С++ обычно использует doxygen, но ни разу не встречал плагинов к нему, который бы запускал бы юниты из документации (это не значит, что их совсем нет, это значит, что я с ними не сталкивался).


            1. cupraer
              04.12.2025 08:04

              Пардон, бес названия попутал. Сам я с плюсами не сталкивался с тех пор, когда тесты вообще никто не писал.


  1. AlexeyK77
    04.12.2025 08:04

    Помимо преимуществ есть и темная сторона, которую непонятно как будут решать. Часто в различных интервью от практиков, особенно из геймдева можно услышать, что раст очнеь плохо подходит для прототипирования, быстрой проверки гипотез и в целом разработка идет медленнее, хоть и на длинной дистанции надежнее. Но вот невозможность делать быстро, пробовать может сыграть злую шутку с этим языком, т.к. не всем требуется не только безопасность любой ценой , но и скорость в балансе с надежностью.

    И еще сильно напрягает, что нет простого способа писать различного рода графовые алгоритмы (без внешних библиотек) и единственным рабочим способом является либо ансейф либо уход в излишний Rc + RefCell, что убивает всю идею на корню.


    1. cupraer
      04.12.2025 08:04

      на длинной дистанции надежнее

      Надежнее, чем что именно?


      1. rsashka
        04.12.2025 08:04

        на длинной дистанции надежнее

        Надежнее, чем что именно?

        Вот и мне стало интересно, о какой такой "длинной" дистанции использования Rust идет речь?


        1. cupraer
          04.12.2025 08:04

          Servo, наверное.

          Авторы языка не осилили написать проект, ради которого язык и создавался. Лучшая история успеха, как мне кажется.


      1. AlexeyK77
        04.12.2025 08:04

        чем на языках без borrow checker (наличие статической типизации естественно подразумевается как мастхэв).


        1. cupraer
          04.12.2025 08:04

          наличие статической типизации естественно подразумевается как мастхэв

          Кем подразумевается? Людьми, которые не понимают, что статическая типизация без завтипов и пруверов — это дутый, никому не нужный, пшик?


          1. Siemargl
            04.12.2025 08:04

            Ну например людьми, которые Глим переписали на Расте, потому что в Эрланге не хватило этой самой статической типизации =)

            Prototype versions of the Gleam compiler were written in Erlang, but a switch was made to Rust as the lack of static types was making refactoring a slow and error prone process.


            1. cupraer
              04.12.2025 08:04

              Ну например автор вот напрямую говорит:

              If Cure’s static analysis functionality is a fighter jet then Gleam’s is a paper plane!

              Он лукавит, конечно, и прибедняется, но в любой шутке есть доля правды. Типизация в Глиме примерно такая же, как в Расте, если что.

              Кому-то почему-то кажется, что (цитата отттуда же, откуда ваша):

              A full Rust rewrite of the prototype resulted in the removal of a lot of tech debt and bugs, and the performance boost is nice too!

              А мне, наоборот, кажется (на основе собственного опыта с Cure, например), что из цитаты можно безболезненно выкинуть пару слов (и пару добавить, для ясности):

              A full Rust rewrite of the prototype resulted always results in the removal of a lot of tech debt and bugs.

              Производительность, разумеется, увеличилась, тут мне крыть нечем. Но это compile time производительность, с которой я готов мириться (да и типы тут ни при чем).


    1. black_warlock_iv
      04.12.2025 08:04

      единственным рабочим способом является либо ансейф либо уход в излишний Rc + RefCell

      Есть ещё способ: кладём всё в арену и дальше граф из индексов в этой арене.


  1. blackyblack
    04.12.2025 08:04

    Это все хорошо, но реализация времени жизни переменных и наследование через трейты, кривая реализация константных данных и недоделанная шаблонизация - убивают язык. Если в соло писать, то все эти проблемы можно обойти и язык становится идеален. Если в команде кто-то неудачно воткнул трейты, то использование Rust становится кошмаром.


    1. miklin-ag
      04.12.2025 08:04

      Что такое у вас наследование через трейты?