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

Лично я делал не меньше 5 попыток на протяжении последних 5-7 лет, прорабатывая, большей частью в свободное и личное время, литературу, некоторые книги по несколько раз, в поисках ответов на простые человеческие вопросы - как свободно писать на Rust и решать, как орешки, ежедневные задачи, не страдая от головной боли и хорошо понимая, что происходит.

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

Немного о себе

Я пишу код с 12 лет и системно занимаюсь коммерческой разработкой с начала нулевых. В основном это Java, Python, PHP, JavaScript и, в последние годы, это Rust. Приходится писать, в основном, mission-critical системный код, нередко еще и высоконагруженных сервисов, стоимость ошибок в котором очень высока, поэтому кода unit и интеграционных тестов в моих проектах обычно больше, чем кода самих проектов. Ну нет возможности предварительно выкатить все на клиентов в качестве "бета" и "собрать ошибки", т.к. любая ошибка может привести к катастрофе и потере коммерческих данных, именно поэтому постоянно и читаешь на тему надежности кода и архитектуры, и пишешь разные виды тестов и исследуешь возможности современных языков программирования по написанию корректного и строго типизированного кода. И именно через этот опыт я и пришел к осознанию большой пользы Rust в подобных mission-critical проектах и использую его идеи и на других языках.

Прочитанные книги

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

Я не буду писать о многочисленных давно прочитанных вводных книгах о разработке (типа "Философия Java" Брюса Эккеля), но самая, думаю, сильно оказавшая на меня влияние профессиональная книга по полезным практикам разработки была "Java: эффективное программирование" Джошуа Блоха. Я перечитывал эту книгу в разных изданиях, несколько раз (как минимум 4 раза), все больше и больше понимая и чувствуя перенесенную необыкновенно опытным автором боль от создания, развития и поддержки частей стандартной библиотеки Java и всемирно известной библиотеки Guava. Эта книга навсегда изменила мой стиль написания кода и тестов, я много лет стараюсь строго следовать этим практикам и это не раз выручало меня в, нередко, сложных проектах с сжатыми дедлайнами.

Но чувство, что в Java (да и в Python) не хватает требуемой для проектов строгости, типизации и точности, не покидало никогда. Несмотря на это, все это время очень помогало активное использование проверки предикатов в конструкторах и методах, enums, generics и инкапсуляции логики в объекты и билдеры ("builders") (да, классическое ООП, но с последними нововведениями и предпочтением агрегации наследованию), а также повсеместное использование иммутабельности ("immutable objects", об этом далее), особенно в проектах, работающих с потоками, в том числе асинхронно (в т.ч. с Netty). Больше 10 лет назад я увлекся Haskell, в надежде научиться проектировать и писать еще более строгий, корректный и устойчивый код, и оно стоило того! Haskell долго не поддавался, т.к. сознание, сформированное промышленным ООП с изменяемыми объектами ("mutable objects"), приходилось выстраивать, по сути, заново. В результате, после нескольких упорных попыток, Haskell, а особенно его развитые алгебраические типы данных и pattern-matching по ним (обо всем этом будет дальше), были поняты не только умом, но и сердцем. Хотя в рабочих проектах я так и не стал использовать Haskell по причинам некой академичности и немного оторванной от реальности архитектуры и семантики языка (например, "ленивое" выполнение и не всегда прозрачно работающая "концевая рекурсия", отсутствие "циклов"), но идеи сильно повлияли на последующее проектирование и практики при написании кода на Java, Python и PHP. Мой предыдущий код после знакомства с Haskell стал выглядеть логически местами "дырявым", что говорит о пользе дополнительного изучения альтернативных языков программирования. После этого код стал еще более ясным, строгим и типизированным, но возможностей используемых языков для обеспечения строгости стало не хватать все больше, особенно в многопоточных/асинхронных приложениях.

Мимоходом было также 2-3 летнее глубокое погружение в расширенный современный Python с продвинутым ООП, декораторами и асинхронностью, но кроме необязательных аннотаций типов, "утинной" типизации и, конечно, мощной, но довольно опасной в больших проектах с людьми разной квалификации "магии" типа "iterator protocol" ничего особо полезного для моих задач найдено не было. Python никогда не защитит тебя надежно от ошибок, связанных с типами, а просто упадет в runtime - такова философия языка и надо ее уважать. И, что еще более важно, у Python (да и других скриптовых языков для быстрого прототипирования) для наших mission-critical проектов иногда очень не хватало скорости.

Интересное, кстати, наблюдение. Наши mission-critical проекты на Java, оказалось, значительно спокойнее поддерживать, из-за, разумеется, компилятора, проверяющего как-никак ... типы, чем поддерживать большие скриптовые проекты. Код на Java не так страшно улучшать и делать более читаемым (да, я про "refactoring"), чем, простите, большие, местами аннотированные, скрипты. Это - непреложный факт и значительный плюс языкам с более строгой моделью типов и компилятором.

В итоге поиски строгости и гибкости типов, компилируемости, скорости и предсказуемости многопоточной работы привели к изучению Rust.

Я проработал, в основном, в личное время, свободное от работы и офисного шума, следующие книги:

  1. "Rust book". Вводная книга по возможностям языка - делай так, получишь так. После 3 раз (в разное время, разумеется, не подряд :-)) внимательной проработки примеров в консоли все равно остались простые вопросы, на которые я так и не нашел в ней ответов. Я бы не рекомендовал начинать с нее изучать язык, к сожалению, из-за перескоков с темы на тему без объяснения семантики и сравнения в другими ЯП (языками программирования).

  2. "Rust book" (c дополнительным интерактивом). В этой версии книги добавили полезные интерактивные примеры и иллюстрации, которые оказались важны для понимания ряда принципов языка, таких как владение (применяется в афинных типах данных), прочитал ее с большим удовольствием, книга показалось значительно более адаптированной, но она также видится сложной для начинающего изучать язык.

  3. "Rust by example". Отличный тренажер для пальцев и ума, т.к. ты экспериментируешь с маленькими примерами кода и компилятором прямо в браузере, попивая кофе. Я проработал ее пару раз с большим удовольствием, но, к сожалению, осталось больше вопросов "почему это работает так", чем ответов. Тем не менее, я рекомендую начинать изучать язык именно с этого ресурса, т.к. наличие вопросов это уже половина успеха, а мышечная память языка пригодиться в дальнейшем! :-)

  4. "Rust by practice". Хорошее дополнение к ресурсу выше, проливающее больше света на некоторые особенности языка. Но, к сожалению, некоторые важные темы оказались для меня не раскрыты. Тем не менее, тоже рекомендую потренировать на ней пальцы, больше запомнить спинным мозгом и собрать вопросы.

  5. "Rustlings". Интересные интерактивные примеры использования языка для решения задач. Прошел с удовольствием и увидел много пользы для углубления в язык. Не все темы, к сожалению, оказались раскрыты. Осталось много вопросов "почему работает именно так". Но ресурс показался очень полезным для практики.

  6. "Rust in action". Хорошая книга, много простых примеров, но мне не хватило, опять, теории. Тем не менее, получил ответы на некоторые вопросы "почему так" и увидел практические примеры ежедневного использования библиотек, которыми стал активно пользоваться. Не рекомендую читать на начальном этапе.

  7. "Rust for Rustaceans". Продвинутая книга, написанная опытным и подкованным в языке профессионалом. Но, мне показалось, в ней много абстрактных рассуждений (в стиле "рассмотрим монады в контексте моноидов в категории эндофункторов"), особенно в контексте многопоточности и асинхронности (обсуждение в лоб типа "Pin" может привести неокрепшие умы к потери сознания), которые без копания в документации стандартной библиотеки начинающему не осилить. Бросил читать под конец, т.к. начал, таки находить ответы на свои вопросы в другом ресурсе, о котором сразу ниже!

  8. "Programming Rust (Fast, Safe Systems Development)". И вот это, наконец-то, свершилось. Попалась эта замечательная книга, где, впервые, материал изложен правильно и последовательно, с хорошими примерами и подробными сравнениями с другими языками программирования (Python, Java, C++, C), и после которой осталось совсем мало вопросов "почему так?". Я рекомендую начинать знакомиться с языком именно с этой книги, особенно если у вас есть опыт работы с другими ЯП.

    Но оставшиеся ответы на простые вопросы о дизайне языка я все же продолжал надеяться получить и прочитал на новогодних праздниках с усердием еще несколько ресурсов и книг:

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

  10. "Rust Atomics and Locks (Low-Level Concurrency in Practice)". Удивительно толковая книга где я впервые увидел простое и наглядное объяснение модели памяти C++ ("объяснение для занятых") и работы atomics в Rust с подробным перечислением моментов с "happens-before" (о которых и только о которых я не раз читал в "JCIP"). Спасибо больше автору! Сейчас как раз перечитываю эту книгу во второй раз.

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

    Вот этот ответ, который, думаю, нужно поставить на первую страницу вводного руководства по Rust, но его, к сожалению, без подготовки никто не прочитает :-):

    "However this is not at all how Rust reasons that this program is bad. Rust doesn't understand that x is a reference to a subpath of data. It doesn't understand Vec at all. What it does see is that x has to live for 'b in order to be printed. The signature of Index::index subsequently demands that the reference we take to data has to survive for 'b. When we try to call push, it then sees us try to make an &'c mut data. Rust knows that 'c is contained within 'b, and rejects our program because the &'b data must still be alive!"

Ну и, конечно, при работе с источниками, нужно прорабатывать примеры в консоли. По моему опыту удобнее всего это делать прямо в браузере! Я использую официальную песочницу Rust playground. А для текущих проектов - бесплатную Visual Studio Code с плагином под Rust.

А можно ли было сократить этот безумно длинный путь?

Да, теперь я понимаю Rust в деталях и быстро и безопасно решаю на нем ежедневные многочисленные задачи. Время было потрачено не зря и полученный опыт постоянно приносит пользу и в Java и в Python и в целом в проектировании и реализации многопоточных высоконагруженных систем. Но какой же ценой это далось? Не зря Rust считают языком с одной из крутейших кривых вхождения, похожей на C++ или Haskell.

Чтобы не читать такой объем литературы и не добывать опыт через практику, нужно лишь было встретить человека, который бы знал все это и просто ответил бы на накопленные мной вопросы (или сразу заглянуть в Rustonomicon и ничего не понять там). Но, увы, не повезло, и пришлось усердно работать и собирать ответы. А может быть я просто сильно любопытный и "не нужно задумываться, если и так правильно работает"!

Коллеги рядом советовали начать задавать вопросы "нейронке" в таких случаях :-) Возможно они правы, надо попробовать, а заодно сходить и к астрологу и психотерапевту и почистить карму (сарказм).

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

С чего же начинать изучать Rust и сколько это займет?

Самой большой моей ошибкой при изучении Rust было излишнее любопытство и обширный опыт многопоточной разработки в Java/Python. Я искал ответы по философии языка и причинам, как в книгах по Java, но их не было. Поэтому, уже сейчас, оглядываясь назад, я думаю, что правильнее начинать изучать его было с другой стороны - с понимания его гарантий, пользы, что именно он дает бизнесу. А остальное понимать по мере прикладного использования. Так начнем же наш путь, друзья!

Гарантии Rust

Код внезапно не "крешится", не оставляет "дыр" и не меняет бизнес-данные

Так сложилось, что более 20 лет назад я сразу начал свой путь, создавая сайты на PHP. PHP еще тогда давал и продолжает давать гарантии безопасной работы скрипта с памятью - пиши что хочешь, все будет работать без крешей и повреждения данных (многие скриптеры и сейчас пребывают в блаженном неведении, что защищены от этого). Еще тогда ходили истории, что разработчики на C/С++ лишены такой чудесной гарантии и у них код может "упасть" (печально известное "UB", "undefined behavior") так, что никто не знает как - от "segmentation fault" до "временно" незаметного повреждения бизнес-данных в памяти и на диске или оставленных на годы вперед дыр в безопасности. Так вот, это одна из гарантий Rust - все, что вы на нем пишете (не используя блок unsafe, что является очень продвинутой возможностью для выжимания максимума из железа и нужно, реально, долям процента) работает с памятью безопасно и не крешится.

Переформулирую более простыми словами, как и обещал, что будет более понятно для "чистых" скриптеров, таких каким был я еще лет 20 назад: "воспринимайте программирование на Rust таким же безопасным занятием, как и скриптинг на Python/PHP/JavaScript и, в некотором смысле, как программирование на Java". У вас в руках резиновые ножницы, можно тыкать друг другу ими в глаза и ничего опасного не случится.

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

Поэтому дам еще одну формулировку. По сути, программируя на Rust, вы пишите не скрипт (а скорость работы скриптов все хорошо знают, особенно под нагрузкой, особенно когда машина многоядерная), а код на С/С++, работающий такой же скоростью и требованиям к железу, т.е. очень быстро и дешево для железа но... у вас программа "не крешится" и эту строгую гарантию дает вам умный компилятор Rust. Надеюсь, вы почувствовали разницу.

Многопоточный код пишется быстро и работает правильно

А вот тут уже многие напрягаются. В PHP/JavaScript нет многопоточности в принципе, многие люди даже на знают про это. В Python многопоточность "мнимая", всегда выполняется только один поток, блокируя память интерпретатора мьютексом ("GIL"), а потоки выполняются друг за другом, что, конечно, очень сильно влияет на производительность скриптов на Python. Много лет в Python ходят разговоры избавиться от этого, но, насколько я помню, обычные однопоточные скрипты от этого будут работать медленнее, поэтому никто сильно не торопится (хотя этим не страдают интерпретаторы Jython and IronPython).

В высоконагруженных проектах я много лет использовал и продолжаю использовать Java как раз из-за хорошей поддержки многопоточности и простой, понятной модели памяти, объясняющей как синхронизируются изменения в памяти между потоками при использовании "volatile", "atomics", "mutexes" и т.п. Разработчики Java, поверьте, хорошо понимают, что такое многопоточность и, что более важно, что может в многопоточности пойти не так. Много лет назад ходила байка, что если в коде используется "synchronized", то его на поддержку и доработку лучше не брать :-) Шутка не лишена смысла. На самом деле в мире Java это реальная проблема - далеко не всегда понято, работает ли класс в многопоточном окружении или нет. Сколько Джош Блох в "Effective Java" и его коллеги в моей настольной книге "Java Concurrency In Practice" не просили добавлять разработчиков документирующие аннотации "@GuardedBy , @ThreadSafe, @NotThreadSafe", никто этого так системно до сих пор и не делает. Это как добавление аннотаций типов в Python - хочешь добавлять, хочешь не добавляй, ничего страшного, компилятора нет, пострадают лишь только пользователи (сарказм). Но проблема тут гораздо опаснее - Java-компилятор просто не способен и никогда, уверен, не будет способен понять, можно ли использовать данный код, модуль, проект в многопоточном окружении или нет. Компилятор не видит и не увидит причинно-следственные связи "happens before". Мне больно про это писать, у меня много больших проектов на Java, но это, к сожалению, факт. Если вы запустите класс/модуль Java, который не приспособлен работать в многопоточном окружении, или смешаете не многопоточный код ("not thread safe") с многопоточным ("thread safe"), то, скорее всего, он запустится, но будет работать неправильно.

А вот в Rust эту проблему удалось решить красиво и это огромное преимущество языка! "Not thread safe" код в Rust просто не скомпилируется. Это сделано через трейты Sync и Send (об этом в следующих постах). Если просто, то реализованный "классом" Sync означает, что на "объект" можно ходить из нескольких потоков (объект в терминологии Java "thread safe"), а Sync означает, что "объект" можно скопировать из стека одного потока бит за битом в стек другого потока и ничего синхронизировать дополнительно не нужно (как можно проще на пальцах рассказал, чтобы вы сформировали очень точную модель происходящего в голове). Система типов компилятора проверяет (наконец-то это получилось сделать в компиляторе, не прошло и 30 лет), будет ли код работать без гонок данных и без "undefined behavior" в многопоточном окружении или нет.

Именно поэтому писать многопоточный, и, в том числе, популярный ныне, асинхронных код на Rust - легко и быстро, как на Python, только с гарантией работы в многопоточном окружении "без скрытых сюрпризов". На самом деле ошибки все равно могут быть, такие как взаимные логические блокировки или неправильно работающая связь "happens-before" при использовании "atomics" и выборе неверного упорядочения памяти (например выбрали "relaxed" вместо связки "release/acquire" в операциях с "atomics") - но все равно ошибок будет на порядки меньше и они простые, логические и их можно покрыть автотестами и исправить. Это на порядки качественнее и строже, чем ситуация с контролем этого в модели памяти и компиляторе Java на данный момент.

Я не упомянул тут о многопоточных приложениях на C/C++, в которых все на порядок сложнее при работе с многопоточностью и ошибок (гонки данных, "UB" разных видов при использовании "atomics" и т.п.) может возникнуть гораздо больше. Достаточно посмотреть на размер и сложность описания модели памяти C++, чтобы понять, что 99% разработчиков ее никогда не прочитают до конца и тем более не поймут. А оно потом как-то будет работать и может правильно на одном железе и, иногда, неправильно на другом.

В общем, краткий итог этой гарантии - писать эффективный многопоточный, в т.ч. асинхронных код на Rust просто и быстро, т.к. проверки на безопасную работу кода в многопоточном окружении делаете не вы, а компилятор через строгую систему типов (а именно трейты Send и Sync). Если многопоточный код на Rust скомпилировался, то в нем останутся только логические не фатальные ошибки, что является гораздо более сильной гарантией, чем полное отсутствие подобной гарантии на уровне компилятора в Java, не говоря уже об отсутствии каких-либо гарантий в C/C++.

Чтобы не быть голословным, приведу пример многопоточного кода на Rust. Не нужно ничего запоминать, если вы "воткнете" сущности в неправильном порядке то код просто не скомпилируется. Не нужно проверять каждый "класс" на "thread safety" больше, это делает компилятор, проникнитесь этим торжеством!:

use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc::channel;

const N: usize = 10;

// Spawn a few threads to increment a shared variable (non-atomically), and
// let the main thread know once all increments are done.
//
// Here we're using an Arc to share memory among threads, and the data inside
// the Arc is protected with a mutex.
let data = Arc::new(Mutex::new(0)); // не нужно запоминать кто куда втыкается!

let (tx, rx) = channel();
for _ in 0..N {
    let (data, tx) = (Arc::clone(&data), tx.clone());
    thread::spawn(move || {
        // The shared state can only be accessed once the lock is held.
        // Our non-atomic increment is safe because we're the only thread
        // which can access the shared state when the lock is held.
        //
        // We unwrap() the return value to assert that we are not expecting
        // threads to ever fail while holding the lock.
        let mut data = data.lock().unwrap();
        *data += 1;
        if *data == N {
            tx.send(()).unwrap();
        }
        // the lock is unlocked here when `data` goes out of scope.
    });
}

rx.recv().unwrap();

Код работает со скоростью C/C++ без "сборщика мусора"

Многие могут и не знать или не задумываться, что в скриптовых языках типа Python/PHP/JavaScript и, даже, в Java/C# память освобождается отдельной подсистемой "сборщик мусора" ("GC", "garbage collector"). Отдельные потоки (и ресурсы процессора) занимаются постоянной очисткой памяти от ненужных данных и вы, как разработчик, платите скоростью приложения и паузами в работе кода за это. В продвинутой, промышленной Java, уже несколько десятков лет с разным успехом борются с задачей, как сделать эту регулярную "сборку мусора" не так сильно влияющей на скорость работы приложения. Как сделать так, чтобы ваше приложение внезапно не замедлялось и не останавливалось при сборке накопившегося "мусора". Именно поэтому игры, которые не должны тормозить, пишут на C++, а не на Java (кроме, пожалуй, "minecraft"). Про это книги пишут, как правильно настраивать "GC" под конкретные приложения. Если, можно сказать, по скорости Java приближается к C/C++ (понятно, почему я тут не пишу про Python/JavaScript), то по расходу оперативной памяти Java (как и Python/JavaScript и т.п.) требуется ее на порядки больше, нередко гигабайты, в то время как аналогичному решению на C/C++ требуется десятки мегабайт.

Так вот, в Rust сборщика мусора ("GC", "garbage collector") ... нет :-) И вы не платите скоростью работы приложения и размером оперативной памяти за накопление и очистку "мусора" только потому, что разработчикам интерпретатора/компилятора было так проще реализовать язык программирования. В Rust очистка выделенной памяти происходит во время выполнения кода, вызов "деструкторов" встроен компилятором в ваш скомпилированный код и никаких потоков очистки мусора в приложении просто нет. Приложение на Rust ведет себя тут как приложение на C/C++ - выделяет и освобождает память когда нужно, полностью контролируя этот процесс из приложения (и при необходимости вы всегда можете в Rust написать свой сборщик мусора, но это нужно очень-очень редко). Но в Rust этот процесс происходит автоматически или, когда вам это очень нужно, с вашей помощью, но с все той же гарантией компилятора по безопасной работе с памятью.

В результате, аналогичный высоконагруженный код на Java в наших проектах потребляет гигабайты памяти, а код на Rust - десятки, редко сотню-другую мегабайт (например в задаче - очистка файлов в s3 со скоростью 3-5к REST операций в секунду). И нагрузка на процессор при этом - в разы ниже (несмотря даже на jit-компиляцию в Java). А если использовать вместо стандартного системного аллокатора памяти jemalloc, то ситуация с расходом памяти кодом на Rust на десятки мегабайт лучше.

При всех плюсах отсутствия сборщика "мусора" пока, даже в продвинутых Go, С# и Kotlin, он есть, а в Rust - его уже нет. Почему? Потому-что в Rust сделали очень продвинутый, вобравший в себя множество современных знаний о языках программирования (и известных проблем), компилятор, использующий афинные типы данных (уникальная возможность, о ней в следующий постах), алгебраические "sum/product" типы и "pattern-matching" по ним, концепции безопасных "shared" и "exclusive" ссылок для оптимизаций ненужных "aliasing" (этой проблеме в современных ЯП не один десяток лет и в Rust ее красиво решили) и строгую систему типов, позволяющую быстро и дешево писать высоконагруженный многопоточный код, работающий со скоростью C/C++ и при этом не "падающий" из за "неопределенного поведения".

И да, после компиляции программа на Rust представляет из себя запускаемую "бинарку", содержащую в себе (по умолчанию) зависимости, как и в Go. Не нужно устанавливать Java или интерпретатор Python или, спасите-помогите, запускать что-то в Docker-контейнере внутри Docker-демона (еще и в Kubernetes). Для запуска Rust-приложения нужно просто запустить компактный "экзешник" и все дела.

Строгая типизация

После долгой работы с большими проектами на Python (и другими скриптовыми языками), в том числе и на их поддержке, часто спрашиваешь себя - хорошо бы, чтобы все значения в программе имели тип и разработчики не пытались передавать разные смыслы в ... строках. Хорошо бы, чтобы у классов был тип, а "iterator protocol" и "decorators" проверялись компилятором не только когда ему хочется и он способен разобраться в аннотациях типов, а ... всегда. А пока этого нет - вносить изменения и улучшать код - страшно. И проекты медленно умирают.

Java выглядит в этом отношении гораздо строже. Но наличие костыля "null" и системная, недостаточно развитая система типов, смешивающая примитивные и объектные типы, от которой начинают появляться слезы в глазах (от грусти), особенно когда смотришь сюда, сюда и, особенно, сюда. И приходит понимание, что через 50 лет вряд ли что-то сильно поменяется, а станет лишь немного строже. И подобная боль почти везде сейчас, например в Go, который, по моему мнению, по строгости и выразительности типов даже до Java недотягивает, а до Rust ему так от Владивостока до Калининграда.

Так вот, в Rust работа с типами сделана, по опыту и ощущению, сильно гибче и сильно строже, с некоторыми оговорками на основе Typeclasses Haskell c мощной поддержкой алгебраических Sum/Product типов. Типа "null" нет в принципе. Мощная поддержка строго-типизированных замыканий ("closures"). После ознакомления с ними и итераторами в Rust видеть давно привычное это уже трудно без слез грусти - разница с точки зрения надежности и строгости, да и скорости работы, просто колоссальная. По сути, вначале, как и разработке на Haskell, вы продумываете и описываете типы в вашем приложении, а затем связываете их создавая/реализуя трейты и все работает, как правило, сразу правильно. Если вы попытаетесь повторить увиденное c помощью Enums или DataRecords в Java, вам придется написать кучу связывающего кода с повышенной вероятностью ошибиться в нем. Достаточно один раз увидеть это и понять, что за мысль я пытаюсь передать. Я не говорю уже о скриптинге на других языках. Качественная эволюция семантики языка в Rust, с учетом того, что давно пора сделать и никто не хочет (риск же, столько кода написано, менять языки страшно и дорого) это делать - очень заметна.

Т.е. вы получаете очень строгий компилятор, дающий гарантию отсутствия "UB" ("крешей" в лучшем случае, незаметного повреждения данных и дыр в безопасности в худшем), проверяющий логику работы с типами (до некоторой, конечно, степени) и предотвращающий известные в других языках популярные ошибки (такие как "null" или типизация всего в "строку"). Вы получаете продвинутый, очень строго типизированный компилятор, взрощенный опытом использования алгебраических типов данных и typeclasses в Haskell, на котором можно быстро написать многопоточное/асинхронное приложение и скорость разработки напоминает, по опыту, скорость создания скриптов на Python!

Вместо итога

Друзья, я надеюсь, после перечисления гарантий и возможностей Rust у вас, как и у меня, как и у многих других разработчиков, решающих mission-critical задачи, потекли потоком слюнки. И это действительно так, инструмент замечательный и на моей лично практике это было доказано не раз. Одна проблемка - надо поучиться его использовать. Что я и буду делать с подробными примерами в моих последующих постах.

На этом у меня всё, совсем скоро увидимся, задавайте вопросы в комментариях и хорошего всем настроения!

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


  1. atues
    04.02.2025 07:45

    Спасибо, интересно. Как вы оцениваете русскоязычные переводы некоторых из перечисленных вами книг? В частности, вот этой: https://www.amazon.com/Programming-Rust-Fast-Systems-Development/dp/1492052590


    1. AlexSerbul Автор
      04.02.2025 07:45

      У меня есть русский перевод книги про атомарность Мары Бос (https://www.chitai-gorod.ru/product/rust-atomarnosti-i-blokirovki-3060006). К сожалению, есть ошибки в переводе смысловые, чтобы их понять и в уме исправить приходится некоторые абзацы перечитывать по несколько раз. Вообще предпочитаю читать в оригинале, т.к. технический английский не такой сложный, как литературный.


      1. atues
        04.02.2025 07:45

        Полностью с вами согласен - сам предпочитаю оригиналы. Что касается упомянутой мной книги, то ее перевод (https://dmkpress.com/catalog/computer/programming/978-5-97060-236-2/) мне показался вполне нормальным, исключая забавные ляпы. Например, на странице 60 в одном (!!!) абзаце для управляющего символа escape приводятся два кода: 27 (правильный) и 37. Очевидно, что это ошибка набора. Для опытного программиста это не проблема; для начинающего - засада


  1. lesha108
    04.02.2025 07:45

    Мне очень понравилась книга Zero To Production in Rust. Прям пошаговый пример создания приложения


    1. 1755
      04.02.2025 07:45

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