Как мы ржавели. История внедрения и обучения

Все говорили — переходи на rust! Начинай пользоваться самым-самым языком, который самый любимый язык на stackoverflow и всё такое.

59ff9c31ee7876d51722ee00e5638ac6.jpg

Я тяжело вздохнул и огляделся. Ну опять двадцать-пять. Ладно, давайте разбираться, как правильно покрывать всё ржавчиной.

Профессионально я программирую уже 17 лет. В далёкие-предалёкие времена я начал изучать программирование с x86 Assembly. Такой жестокий подход к изучению компьютера дал мне глубокое понимание того как работает процессор и что делают другие языки программирования. Учить новые языки для меня никогда не было проблемой. Я их просто учил. C, C++, шарпы, ГО, тысячи разных скриптов и фреймворков. Чего уж там, ещё один язык, можно и выучить.

Ага. Только вот не с этой ржавой развалюхой. Я просидел две недели над официальным руководством, пялился в VSCode и не мог выдавить из себя больше чем fn main () {}. Ну какого же чёрта?

Я просто закрыл глаза, открыл руководство на рандомной странице и начал его читать. И прикол в том, что я понял о чём там говориться. И тут до меня дошло… Руководство написано наизнанку. Начало и конец ничуть не лучше чем в Мементо Кристофера Нолана. Они как-то умудрились запутать в общем-то нормальное руководство.

После этого всё пошло получше. Ржавые шестерёнки заскрипели и начали крутиться. Программа пошла-поехала. Если хотите знать как я внедрил rust в продакшин, как я ему научился и что из этого вышло — добро пожаловать под кат. Реальные люди, коммиты, примеры и никаких todo-list и хеллоу-ворлдов. Ржаветь так ржаветь.

Для начала — быстро расскажу что я заменил на rust. Жил да был лаборант. Ему сказали, мол, напиши программку — подключается вот к этой системе, сгружает бинарные данные по сети с порта, конвертирует их в удобочитаемый формат и сохраняет всё в базу данных. Лаборант был очень любознательным выскачкой. Почему-бы не показать себя? Он и показал. Написал программу на C# которая подключалась к серверу и тянула данные с компьютера. Преобразовывала их в JSON, который был доступен через POST API. С шарпами он был не очень дружен, посему сохранять в базу данных всё решил на Node.js, который эту программу на шарпах дёргал, получал JSON, делал немного логики и гнал в базу данных (postgres). На всё это дело был прикручен вэб интерфейс, который позволял крутить настройки. Всё бы хорошо, но поиска по полученным данным у нас не было. Лаборант сказал: «Да не вопрос! Щас всё сделаю!» и поставил на всё это дело Elasticsearch. Поддерживать всё это дело стало страшно, посему лаборант сказал: «Щас всё починю!» и написал кучу Doker файлов, положил всё в контейнеры, сделал docker-compose и всё заработало.

Примерно через год я пришёл и посмотрел на всё это дело. Полный дамп базы данных весил 250Мб. Прирост данных составлял 50 метров в год. Чего там, тривиальная задача. Но всё это крутилось на выделенном сервере с 32 гигами оперативки и 24 процессорами. Сервер выл и жужжал потому что приходилось гнать 16 докер-контейнеров с кластером Elasticsearch, который постоянно рушился от перегрузки.

Ай-я-яй. Подумал я. Надо что-то делать. Времени было достаточно, я и решил, почему-бы не попробовать rust?

Акт номер 0: Вступление

Конечно, есть видео на утубиках и всё такое, но я же пришёл из другой эры. Когда мы учили С++ нам в помощь приходила только свежая болванка с дампом MSDN. Мы любим нормальные доки.

Ну и что-бы вы подумали? Доки есть. И они даже в очень приличной форме.

The Rust Programming Language — замечательная книга, которая раскрывает все основные аспекты языка и показывает как им пользоваться. Книга примечательна тем, что сначала заводит вас в непроходимый омут с чертями, лешими и пресмыкающимися, а после пытается вас оттуда вытащить. (600 страниц)

The Rust Reference — полное, досканальное и дотошное руководство, которое расскажет и покажет что делает каждая закорючка на экране. Хотите знать как rust разложит в памяти ваш замечательный enum? Вам сюда. Абсолютно человеко-нечитабельное руководство, которое рассказывает обо всём. (300 страниц)

Rust Compiler Error Index — rust имеет замечательную фитчу. Ихней компилятор настолько разговорчив что вам даже не понадобиться резиновая уточка для разговоров. Во время написаниия программы вы будете наслаждаться простынями всяких ошибок компилятора. Просто невероятное количество их. В этом замечательном томике все эти ошибки шибко расписанны и рассказаны. (400 страниц)

Rustonomicon — каждый кто более-менее начал работать с rust будет вам рассказывать про «Номикон». Эта книга по чёрной магии, которая позволяет вам убивать ваших врагов и привораживать по кофейной гуще. Все страшные стороны работы rust раскрыты именно здесь. (200 страниц)

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

Акт номер 1: О чём вы, Морфеус?

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

Но давайте посмотрим на мир более серьёзно, почему же всё-таки rust? Код, генерируемый rust использует LLVM как компилятор. Что это нам даёт? В общем-то неплохую переносимость на разные платформы, при полном отсутствии потребности в разных фреймворках. Программа на rust будет практически такой-же быстрой как и программа на Си. Конечно куча флейм-троллей сейчас может начать комментить в стиле холивора «Раст против С» и наоборот, но чёрт бы с ними.

Какая разница если rust такой же быстрый как Си или если Си его обгоряет на 5%? Забейте. Не вникайте в это. Момент в том, что rust это системный язык программирования, который позволяет работать с вашим железом на очень низком уровне и не содержит в себе миллионы абстракций, фреймворков и тому подобное.

Двайте посмотрим вот на что:

Проект написанный на .net core 3: Исполняемый файл — 6 Мегабайт.

Проект написанный на rust: Исполняемый файл — 240 Килобайт.

Хаха! Но эти цифры — подстава и обманка! Хреновые маркетологи.

Я беру этот проект, копирую исполняемые файлы на флешку и запускаю их на другом компьютере (Windows 7).

И что бы вы думали? .net core проект не запускается! Почему? Нет фреймворка.

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

Результат? 89 Мегабайт!

Хаха! Вот так тебе и надо, .net! Хитрец! Пытался меня обмануть!

Ладно, теперь запускаем наш проект на rust. Вот, смотрите, просто кликаешь сюда и…

The program can’t start because VCRUNTIME140.dll is missing from your computer. Try reinstalling the program to fix this problem.

Ладно, пересобираем проект и вшиваем все зависимости в исполняемый файл и… 569 Килобайт.

Хм. Да в общем-то и неплохо. Учитывая то, что эта штука подключается к базе данных и всё такое. Вот такой он, системный язык.

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

Акт номер 2: Учиться, учиться и ещё раз учиться

Вот вам пример. На странице 38 официального руководства (600 страниц) показан простой код вот этой замечательной программы:

2ec8d2e082afebe9c07850274dff6cb0.png

Программа просто замечательная. Игра «отгадай число». Компьютер загадывает число, а ты его сидишь и отгадываешь. Тривиально, Ватсон, не так ли?

Нет.

Ничегошеньки не тривиально. Эта «простая» программа для «новичков» перегружена подводными камнями, минами и противовертолётными бомбами.

Прикол в том, что несмотря на всю свою сиобразность, rust включает в себя неимоверное количество синтаксического сахара, и всяких плюшек, которые компилируются в приличный и быстрый код. Все эти плюшки запутывают тебя досмерти, если в них не разобраться. И всё это тебе выдаётся на блюдечке с голубой каёмочкой, пока тебе говорят «Вот скушай наш ᾋﯓﻼ҉ᴪᾠﭥ», он вкусный.

Нет, нет, нет и ещё раз нет!

Первая глава этого руководства должна гореть в печи!

Давайте я вам покажу как его читать.

Начинать читать это руководство нужно с главы №4 под названием Understanding Ownership. Разбираемся в правилах владения переменными. Если вы начали работать с rust, то эти правила придётся учить и разбираться в них. Это именно то, почему и как rust такой замечательный.

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

Но если нужно написать код который двигается побыстрее, то пожалуйста, заменяем сборщик мусора на ручное управление памятью. Мы в ассемблере так всегда и делали. Результат?

Результат правильного управления памятью.Результат правильного управления памятью.

Накосячить с памятью — это просто святое.

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

Что делать? Либо вы отдаёте все в руки вашего фреймворка, который может быть неповоротливым и жрать кучу памяти, либо вы делаете всё в ручную, но это страшно.

В rust всё это было решено следующим образом: Создали ссылку на новый объект — компилятор быстренько встроит туда код, который запросит память у системы, прочистит и инициализирует эту память. А после того, как ваша ссылка уходит за зоны видимости, то компилятор автоматически добавит код, который память по этой ссылке освободит и отдаст обратно системе.

Всё просто и красиво. Чики-пуки! Ага.

Если вы не разберётесь как эти правила работают вы будите сидеть перед монитором часами, рыдая и умоляя компилятор собрать ваш код. Код в котором вы управляете памятью неправильно не будет компилироваться. Вот тебе и всё.

Пример:

let s1 = String::from("hello");

let s2 = s1;

println!("{}, world!", s1);

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

Ладно, хватит вас пугать.

Читаем главу Ownership и разбираемся как эта память работает. Благо, глава написана очень толково и рассказывать обо всём по порядку.

Акт номер 3: Все по порядку

После главы №4 надо прочитать главу №3.

Удивительно, но у некоторых людей странное понимание слова «Номер». Они их путают местами.

В главе №3 рассказывается об основных концептах языка. Как и с чем работать, как создвавать переменные, ветвить программу и всё такое. Все эти данные на самом деле завязанны на понимание главы 4.

Ладно, разобрались с азами. Теперь пройдитесь по главам 5 (Использование структур), 6 (Enum и паттерны), а затем, понятное дело, прочитайте главу 8 (Коллекции). Это даст вам понимание основных объектов и как с ними работать.

После этого рекомендую перечитать главу №4. Открываются новые горизонты.

Теперь можно смело переходить к главе №9 (Улаживание ошибок).

В rust есть один прикол — в нём нет такого понятия как Null. Если вы погуглите «Null is a mistake» вы поймёте о чём я говорю. Приличное количество разработчиков ещё с 1965 года жалуются на то, что такое понятие как Null в значении пременной было неправильной идеей и не должно было существовать.

Rust избавился от идеи Null и это приводит нас к очень интересной, приятной и понятной системе обработки ошибок. Единственное что — вы такую систему видели не часто, и её надо знать, если хочется работать в rust.

Глава №7 рассказыват о том, как распилить программу на куски, чтобы не нужно было писать весь код в одном файле. Далее можно смело переходить к главе №10, и учить про дженерики, трейты и тому подобные финтивлюшки.

После этого рекомендуется перечитать главу №4. Сознание расширяется.

За главой 10 следуют главы 13 и 17. Они объясняют что rust имеет черты как функциональных так и объектно-ориентированных языков, и рассказывают о том, как пользоваться всеми этими прибамбасами.

После всего этого можно переходить к двум самым зубодробильным главам в этой книге. 15 и 16. При прочтении этих глав рекомендуется перечитывать главу №4 после каждого абзаца. Почему? А всё потому что если вам пришлось долго разбираться в том, как происходит управление памятью в обычной программе, то понять управление памятью в многопоточных программах будет занятием для чемпионов.

Но вы на самом деле не бойтесь. Сами по себе главы в книге написаны дельно, просто порядок неправильный.

Ладно, после этого — вы в доме хозяин. Можно начинать перечитывать книгу с главы №1 и потом переходить к главе №2. Приведённая задачка на нахождение числа покажется вам просто детской игрушкой (коей она и является). После идите по всем остальным главам и вдумчиво читайте. После всего этого вы сможете писать на rust.

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

  1. Вы будете знать что на самом деле написано в Номиконе. (Хотя и лезть туда вы не будете).

  2. Вы сможете читать The Rust Reference.

С этими знаниями вы на самом деле можете сесть и начать разрабатывать свой первый проект, который будет посложнее todo-list.

Акт номер 4: Реальности

Реальная разработка принесёт свои определённые знания.

Опыт и мениния, которымы я с вами поделюсь, является чисто моим и у вас может сложиться другая картина восприятия мира.

Первое что вы поймёте — компилятор не прощяет огромного количества ошибок. Да, конечно, это хорошо, ибо потом эти ошибки не пройдут в продакшин. Но и с другой стороны — собрать прототип на rust за пару минут не получится. Если вы где-то сказали «Тут должно быть число, но в 1% случаев его может не быть» то вам придётся писать эту ветку обработки 1% случаев. Код не скомпилируется.

Те кто не освоил главу №4 будут играть в замечательную игру «Уломай компилятор скомпилировать твой код». Все правила управления памятью, которые делают rust таким мягким и пушистым, заставляют вас думать по другому об устройстве вашей программы. Никаких висячих хвостов или незаконченных кусков кода. Если вы просто пытаетесь написать программу от балды, то скорее всего вы потратите часы на то, чтобы привести её в порядок и скомпилировать.

Далее идёт каталог программного обеспечения crates.io.

Это сайт с которого можно слить приличное количество модулей и библиотек для вашего rust приложения. Сайт на самом деле содержит огромное количество программного кода. Но реальность не всегда замечательна.

Например, мне пришлось потратить порядка 2х часов чтобы найти нормальную библиотеку для работы с postgres. Всё было замечательно, и я работал с этой библиотекой порядка 10-ти часов, пока не наткнулся на очень интересную проблему. В rust нет встроенного типа для валютных операций. Float не подходит, лужно было что-то типа Decimal. Decimal мне удалось найти в виде библиотеки, которую я нехотя скачал и добавил в свой проект. И тут внезапно выясняется, что драйвер для posgresql понятия не имеет, как конвертировать этот Decimal в Postgres Decimal.

Пришлось потратить 4 часа на разбирательства я написание конвертера.

Далее, достаточно скверный момент в rust — документация к пакетам. Да, в rust есть встроенная система документирования кода, и ты всегда можешь слить доки которые авто-генерируются из комментариев к твоему коду. Но вот эти доки зачастую оставляют желать лучшего. Тот же Decimal, из примера чуть повыше, имеет замечательную документацию, в которой сказано следующее — «Это Decimal. Позволяет работать с Decimal. Работайте. Благослови вас Господь!»

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

С другой стороны, развитые проекты имеют хорошие сайты, на которых публикуется тонна дополнительной документации. Например tokio, actix и rocket.rs. Эти проекты (вы обязательно о них услышите, как только достаточно проржавеете) предоставляют огромное количество дополнительного чтива.

Акт номер 5: Итоги?

Старый добрый дымящийся и ревущий сервер успокоился и перестал выходить из себя. 100-мегабайтное приложение на C# было заменено 564 килобайтами кода написанного на rust. 200 мегабайт памяти которые потребляло приложение на шарпах теперь превратились в 1.2 метра RAM для программы на rust. (Заодно избавился от двух докер-контейнеров в которых это приложение работало).

После трёх недель изучения языка, мне потребовалось примерно неделя на то, чтобы переписать приложение. Ну, до кучи, я посмотрел на всё это дело, состоящие из докеров и yaml файлов, и начал его разбирать. Убрал оттуда Elasticsearch. Ко дну пошли семь докер-контейнеров. Как выяснилось, большинство поисков по данным было сделанно следующим образом — пользователь выбирает даты в Elastic и после этого скачивает csv. Затем весь поиск происходит в Excel. Elastic был заменён простой командой в cli, которая позволила сливать csv по дате, благо это можно было делать и из postgres.

Далее пришёл черёд web интерфейса для настроек. (Ещё пара контейнеров улетает в океан). Настроек оказалось порядка 12-ти и я их запихнул в TOML файл, который просто читается моим rust приложением. Ушёл ещё один докер-контейнер. Кит остался плавать один, без контейнеров. Он обиделся и уплыл во свояси. Докер можно было сносить.

Я посмотрел на сервер. Остался только postgres и моё замечательное rust приложение. Слил всё это на raspberry Pi и показал всем чудеса оптимизации.

В приведённом выше примере Rust был не единственной причиной почему серверы перестали визжать от перенапряжения. Дизайн приложения отсутствовал как класс. Всё было сделано наспех и безолаберно склеено докером, ибо так всё и должно быть. Я думаю, что если бы я полностью переписал всё на C#, то итоговое приложение получилось бы в 100 мегабайт и сидело-бы себе, откушавши 250 мегабайт памяти.

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

Акт номер Last: вывод

Rust это годный язык программирования. Он не предназначен для прототипирования приложений. Разработать agile проект на rust за 20 минут не получится. Rust требует хорошего понимания системного программирования. И хотя rust и не такой сложный как Си в управлении памятью и ссылками, он требует очень аккуратного и правильно спланированного дизайна приложения, и, так же как и Си, не прощяет ошибки. Но в отличии от Си, очень многие из этих ошибок будут не прощены на этапе компиляции, ещё до того как ваше приложение пойдёт в продакшин.

Использовать rust в изолированных проектах? Да! Если знаете как.

Заменить простую системную утилиту, которую какой-то лаборант написал для вас на node.js за 2 часа и которая перегружает сервер? Да! Вперед!

Добавлять rust в существующие проекты на C и C++? Подумайте ещё раз. Rust заставляет вас думать по-другому. И все те замечательные плюшки управления памятью, которые существуют в rust есть и для C и С++. Их конечно нужно установить отдельно и настроить как полагается, но если проекту уже много лет, может не стоит. Пусть будет как будет.

Так что давайте, ржавейте. Тут весело.

PS. Хобби — разборка. Разбираю на части перегруженные серверные приложения. Сношу докер. Снижаю счета за AWS.

© Habrahabr.ru