Rust vs. state

Введение

Rust не принято считать объектно-ориентированным языком: в нём нет наследования реализации; инкапсуляции на первый взгляд тоже нет; наконец, столь привычные ООП-адептам графы зависимостей мутабельных объектов здесь выглядят максимально уродливо (вы только посмотрите на все эти и !)

Правда, наследование реализации уже как несколько лет считают вредным, а гуру ООП говорят очень правильные вещи вроде «хороший объект — иммутабельный объект». Вот мне и стало интересно: насколько хорошо объектное мышление и Rust сочетаются друг с другом на самом деле?

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

Он был выбран не просто так: этому же паттерну посвящена глава из The Rust Book. Цель той главы была в том, чтобы показать, что объектно-ориентированный код на Rust пишут только плохие мальчики и девочки: здесь вам и лишний , и тривиальные реализации методов нужно копипастить во все реализации типажа. Но стоит применить пару трюков, и весь бойлерплейт пропадёт, а читаемость — повысится.

Сопоставление с образцом в местах объявления переменных

Операция

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

Здесь произведена деструктуризация: такое сопоставление введет переменные и , которые будут проинициализированы значением полей и объекта структуры , который возвращается вызовом . При этом сопоставление корректное, так как типу выражения справа соответствует образец типа слева. Похожим образом можно взять, например, два первых элемента массива:

И сделать много чего еще. Самое замечательное, что подобного рода сопоставления производятся во всех местах, где могут вводиться новые имена переменных в Rust, а именно: в операторах , , , , в заголовке цикла , в аргументах функций и замыканий. Вот пример элегантного использования сопоставления с образцом в цикле :

Метод , вызванный у итератора, сконструирует новый итератор, который будет перебирать не исходные значения, а кортежи, пары «порядковый индекс, исходное значение». Каждый из этих кортежей при итерациях цикла будет сопоставляться с указанным образцом , в результате чего переменная получит первое значение из кортежа — индекс, а переменная — второе, то есть символ строки. Далее в теле цикла мы можем использовать эти переменные.

Другой популярный пример использования образца в цикле :

Здесь мы просто игнорируем значение итератора, используя образец . Потому что номер итерации в теле цикла мы никак не используем. То же самое можно сделать, например, с аргументом функции:

Или при сопоставлении в операторе :

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

Синтаксис и код

За осно­ву син­так­си­са в Rust взят син­так­сис из C и C++.Например, клас­си­че­ский «При­вет, мир!» на Rust выгля­дит так:

fn main() {
    println!(«Hello, world!»);
}

Если вы зна­ко­мы с подоб­ным син­так­си­сом, то смо­же­те быст­ро начать писать и на Rust. Дру­гое дело, что в Rust есть свои осо­бен­но­сти:

  • пере­мен­ные менять нель­зя, а если нуж­но — при объ­яв­ле­нии ука­зы­ва­ют клю­че­вое сло­во mutable;
  • все коман­ды внут­ри услов­ных опе­ра­то­ров, цик­лов и дру­гих блоч­ных кон­струк­ций обя­за­тель­но брать в фигур­ные скоб­ки, даже если там будет все­го одна коман­да;
  • аргу­мен­ты у услов­ных опе­ра­то­ров, напри­мер if или while, в скоб­ки брать не нуж­но;
  • при объ­яв­ле­нии пере­мен­ной мож­но исполь­зо­вать услов­ный опе­ра­тор:

let x = if new_game() { 4 }
    else if reload() { 3 }
  else { 0 }

Послед­нее раз­бе­рём подроб­но. При такой запи­си пере­мен­ная x будет рав­на четы­рём, если функ­ция new_game() вер­нёт зна­че­ние true. Если это­го не слу­чит­ся, ком­пи­ля­тор вызо­вет функ­цию reload() и про­ве­рит, что полу­чи­лось. Если true, то x при­мет зна­че­ние 3, а если и это не сра­бо­та­ет — то x ста­нет рав­ным 0.

Ещё в Rust есть срав­не­ние пере­мен­ной с образ­цом. В зави­си­мо­сти от того, с каким образ­цом сов­па­ло зна­че­ние пере­мен­ной, выпол­нит­ся та или иная функ­ция:

Вариант 3

В итоге я нашел хорошую библиотеку для создания деревьев, которая называется rust-forest. Она дает возможность создавать узлы, указывать на узлы умными указателями и вставлять и удалять узлы

Однако, реализация не позволяет добавлять узлы разного типа T в один граф, и это важное требование библиотеки вроде nanogui

Взгляните на этот интерактивный пример. Он немного длинноват, поэтому я не добавил полный листинг прямо в статью. Проблема в этой функции:

» Запустить в интерактивной песочнице

К слову, эту странную штуку можно обойти, но я все равно не понимаю, почему это вообще проблема.

» Запустить в интерактивной песочнице

Заключение

Проблемы, с которыми я столкнулся при реализации способов 1, 2 и 3, наталкивают меня на мысль, что четвертый вариант со связкой с С — это единственный подходящий для моей задачи способ. И теперь я думаю — зачем делать связку с С, когда можно просто написать все на С? Или С++?

У языка программирования Rust есть положительные черты. Мне нравится, как работает Match. Мне нравится общая идея типажей, как и интерфейсов в Go. Мне нравится пакетный менеджер cargo. Но когда дело доходит до реализации деталей типажей, подсчета ссылок и невозможности переопределить поведение компилятора, я вынужден сказать «нет». Мне это не подходит.

Я искренне надеюсь, что люди продолжат улучшать Rust. Но я хочу писать игры. А не пытаться победить компилятор или писать RFC, чтобы сделать язык более подходящим моим задачам.

Примечание переводчика

Я не понял, что имеет ввиду автор, когда говорит «для корректной реализации такого счетчика нужно резервировать память глобально», как если бы это поведение было нетипичным для других языков, в частности С и С++. В них тоже нужно класть переменную в динамическую память если хочешь сохранить ее после завершения функции, верно?

К тому же, «нет простого способа указать компилятору не удалять переменную автоматически когда она выходит из области видимости» — похоже, просто неверное утверждение, потому что функция std::mem::forget создана специально для этого (из обсуждения на реддите).

Хорошие обсуждения статьи:

  • На реддите /r/programming
  • На реддите /r/rust (заметьте, что в сообществе Rust первый комментарий к статье, которая ругает Rust, это призыв улучшить документацию)
  • На HackerNews

unsafe-блоки

Давайте сразу обратимся к примеру и посмотрим, как в Rust работают итераторы:

Здесь мы создаем массив из трех элементов, затем получаем итератор на него и добавляем к нему два адаптера — и , после чего собираем итератор в новую коллекцию типа . Этот код — достаточно высокоуровневый и довольно типичный для многих высокоуровневых языков (таких как JavaScript). Но давайте теперь посмотрим, как реализован объект типа , который мы получаем в результате вызова метода :

Обратите внимание, что структура содержит в качестве своих полей два указателя: и (строки 2 и 3). Из-за того, что эти указатели — это обычные Си-совместимые указатели (правда дополнительно требует, чтобы указатель не был нулевым), довольно низкоуровневые ссылочные типы, их время жизни никак не отслеживается borrow checker’ом

Поэтому заданное в объявлении структуры время жизни ссылки (1) мы вынуждены добавить в «фантомное» поле с типом нулевой размерности (4). Иначе время жизни окажется никак не используемым внутри структуры, что приведет к ошибке компиляции. То есть, другими словами: мы хотим сделать безопасный итератор, который ссылается на элементы коллекции, по которой он итерируется, и для того, чтобы он был безопасным, нам нужно учитывать время жизни ссылок. Но наша внутренняя реализация основана на указателях и потому не подразумевает никакого отслеживания времен жизни со стороны компилятора. Поэтому мы должны гарантировать своей реализацией безопасность кода, работающего с указателями (в unsafe-блоках, подобных 5), и тогда можно реализовать безопасный внешний API по всем правилам работы в safe Rust.

Это очень наглядный пример того, что представляет собой Rust на самом деле. Это высокоуровневый, безопасный язык, в котором есть низкоуровневые небезопасные возможности. Тут граница, по которой одно отделяется от другого — это определение типа, а блоки unsafe выступают маркером того, что в реализации используются весьма низкоуровневые и небезопасные средства (на самом деле в общем случае граница проходит через определение модуля: пока в язык не будет добавлена возможность помечать поля как unsafe, потенциально небезопасным становится весь код в пределах модуля, если на поведение unsafe-методов влияет содержимое полей структуры).

Важный вывод, к которому мы здесь приходим, состоит в том, что Rust — самодостаточный язык. Его высокоуровневые возможности вполне реализуются на низком уровне им же самим. И наоборот: из низкоуровневых «кирпичей» в Rust можно конструировать высокоуровневые блоки, скрывая детали реализации за безопасным API.

Теперь должно быть понятно, что unsafe, который тут и там встречается в стандартной библиотеке Rust — это не баг, а фича. Есть довольно популярный упрек к Rust со стороны: дескать, какой же это безопасный и высокоуровневый язык, если у него в сплошные unsafe-блоки? Он либо тогда должен быть весь unsafe, либо полностью safe. Но преимущество Rust как раз состоит в том, что он позволяет делать и то и другое, при этом отделяя одно от другого. Это одна из причин, почему Rust по-настоящему универсальный язык программирования.

Масштаб работ

В оригинальной статье моделировался workflow поста в блоге. Проявим фантазию и адаптируем исходное описание под суровые русские реалии:

  1. Любая статья на Хабре когда-то была пустым черновиком, который автор должен был наполнить содержимым.
  2. Когда статья готова, она отправляется на модерацию.
  3. Как только модератор одобрит статью, она публикуется на Хабре.
  4. Пока статья не опубликована, пользователи не должны видеть её содержимое.

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

Листинг ниже демонстрирует код, соответствующий описанию выше.

пока выглядит следующим образом:

Это проходит все ассерты, кроме последнего. Неплохо!

Несколько советовПравить

Во-первых, с самого начала игры займитесь распределением обязанностей в своей команде. Это серьёзно повысит её эффективность. Один-два опытных охотника, человек, который изучает все необходимое и т.д. То же касается и использования оружия. Допустим, у вас есть М4, и один член команды не умеет пользоваться автоматическим оружием. Пусть возьмёт пистолет, а впоследствии, в процессе развития, болт. Лучше иметь в команде пару снайперов, пользу от которых вы сразу же ощутите во время рейдов. В дополнение ко всему, легче привыкнуть к какому-либо одному оружию.

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

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

ИГРА В ОДИНОЧКУ

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

Первым делом нарубите дерева и камня, и сделайте кирку с топором. Постройте небольшой дом со спальным мешком, шкафом с инструментами, ящиками для хранения, и желательно с костром и печью. Если на сервере присутствуют специальные наборы, или же «киты», не стесняйтесь ими пользоваться. Команда /kit обычно используется для получения этих самых наборов. Строиться лучше подальше от РТ, дабы радиация вам не навредила. Тем не менее, на РТ порой можно найти полезные предметы, поэтому Вам стоит туда заглядывать время от времени, заранее снарядившись необходимой для этого ээкипировкой.

Сообщество

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

  • Только в этом году мы обработали более 2,25 петабайта (1PB = 1000 ТБ) различных версий компилятора, инструментария и документации!
  • В то же время мы обработали более 170 ТБ пакетов для примерно 1,8 миллиарда запросов на crates.io, удвоив ежемесячный трафик по сравнению с прошлым годом.
  • Когда Rust версии 1.0 был выпущен, можно было подсчитать количество компаний, которые использовали его в промышленной разработке. Сегодня его используют сотни технологических компаний, а некоторые из крупнейших технологических компаний, таких как Apple, Amazon, Dropbox, Facebook, Google и Microsoft, решили использовать Rust в своих проектах для надёжности и производительности.

В чём идея языка Rust

Авто­ру язы­ка нра­ви­лась ско­рость рабо­ты и все­мо­гу­ще­ство язы­ка C++ и надёж­ность Haskell. Он поста­вил перед собой зада­чу сов­ме­стить оба этих под­хо­да в одном язы­ке, и за несколь­ко лет он собрал первую вер­сию язы­ка Rust.

Rust пози­ци­о­ни­ру­ет­ся как ком­пи­ли­ру­е­мый систем­ный муль­ти­па­ра­диг­маль­ный язык высо­ко­го уров­ня. Сей­час пояс­ним, что это зна­чит.

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

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

Муль­ти­па­ра­диг­маль­ный зна­чит, что в язы­ке соче­та­ют­ся несколь­ко пара­дигм про­грам­ми­ро­ва­ния. В слу­чае Rust это ООП, про­це­дур­ное и функ­ци­о­наль­ное про­грам­ми­ро­ва­ние. При­чём, ООП в Rust при­шло из C++, а функ­ци­о­наль­ное — из Haskell. Про­грам­мист может сам выби­рать, в каком сти­ле он будет писать код, или сов­ме­щать раз­ные под­хо­ды в раз­ных эле­мен­тах про­грам­мы.

Как синхронизировать Rust и Rust+

  • Для начала вам необходимо зайти в Rust, а затем подключиться к серверу;
  • Далее нажимаем ESC, переходим во вкладку Rust+. В данной вкладке нажимаем на кнопку “Pair with server”.

Rust vs. stateМеню подключения сервера к Rust+

Далее берём в руки телефон, на котором уже открыто приложение Rust+. На него придёт уведомление, а в открывшемся окошке нажимаем на кнопку “Pair server”;

Rust vs. stateПодключение сервера в Rust+

В нижней части интерфейса есть 5 кнопок:

Hub – основная информация о сервере. Онлайн, время, информация о карте, время с последнего вайпа, включение и отключение уведомлений с этого сервера.

Rust vs. stateИнформация о сервере в Rust+

Map – карта сервера, на котором вы играете и авторизовались.

Rust vs. stateКарта в приложении Rust+

Team – информация о команде + чат для общения.

Rust vs. stateКомандный чат в приложении Rust+

Devices – подключённые устройства в игре (турели, пушки, автоматический свет и многое другое).

Rust vs. stateУправление устройствами в приложении Rust+

News – новости, которые отображаются и в самой игре в аналогичной вкладке. Здесь в основном выходят разные DEV-блоги, поэтому теперь вы будете получать информацию о новостях сразу со своего приложения.

Rust vs. stateВкладка News

В верхней части экрана есть ещё 2 иконки:

В верхнем левом углу располагается иконка с подключенными серверами, а в правом верхнем углу – настройки приложения.

Вайп в Rust – это хорошо или плохо?

Игроки, которые только начинают свой путь развития в Расте, зачастую начинают возмущаться, когда узнают, что в скором времени все их накопленные ресурсы и постройки будут удалены в результате вайпа. На самом деле все эти возмущения напрасны. Почему, спросите вы? Ну вот вы представьте сами: вы уже накопили много ресурсов, зарейдили кучу домов, онлайн на сервере сильно «просел», и играть, по сути, не с кем. Что будет дальше? Скорее всего, вы уйдете с этого сервера или вообще прекратите играть. А вайп дает новую жизнь, заставляя игроков заново заходить на проект и пробовать повторно построить другую базу с учетом прошлых ошибок, сразу появляется желание доминировать, отомстить тем, кто вас зарейдил в прошлый раз.

В итоге получается, что вайп – это своего рода цикл, неизбежный цикл, который будет сменяться постоянно. Да, сначала вам будет трудно осознавать, что очистка карты и всех ваших домов, ваши потраченные десятки часов в игре будут потеряны, но в скором времени вы привыкнете, спокойно отыграете несколько дней подряд и будете готовы к новому вайпу. Это Rust, это выживание!

Что вошло в стабильную версию 1.30.0

Rust 1.30 — выдающийся выпуск с рядом важных нововведений. Но уже в понедельник
в официальном блоге будет опубликована просьба проверить бета-версию Rust 1.31,
которая станет первым релизомRust 2018». Дополнительную информацию об этом
вы найдёте в нашей предыдущей публикации What is Rust 2018».

Процедурные макросы

Ещё в Rust 1.15 мы добавили возможность определятьпользовательские derive-макросы».
Например,с помощью , вы можете объявить:

1
2
3
4
#derive(Serialize, Deserialize, Debug)]
struct Pet {
    name String,
}

И конвертировать в JSON и обратно в структуру,используя . Это возможно
благодаря автоматическому выводу типажей и  с помощью процедурных
макросов в .

Обобщенные типы

Помимо того, что обобщенные типы избавляют от написания шаблонного кода, они являются отличным инструментом абстрагирования и высокоуровневой спецификации поведения. Вот что я имею ввиду:

Тут уже сразу по сигнатуре функции понятно, что мы можем принять в аргументах объекты одного и того же типа , для которого реализовано отношение частичного порядка. Таким образом, от указания конкретного типа, мы переходим к указанию неких характеристик используемого типа с помощью типажей (traits), которые могут быть присущи самым разным типам. Можно указывать и достаточно нетривиальные зависимости между типами. Однако сами по себе характеристики, ровно как и соответствующие им типажи, могут быть как высокоуровневыми, так и низкоуровневыми. То есть они могут характеризовать тип как со стороны требований решаемой задачи, так и со стороны требований реализации. Вот небольшой пример из стандартной библиотеки:

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

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

Можно сделать так, что будет возможно сконструировать только с помощью некоего сервиса, который либо выдает новое число из глобальной последовательности идентификаторов, либо десериализует значение из ранее сконструированного и сохраненного. (Подробнее о преимуществах подобного подхода к проектированию типов вы можете прочитать в статье Парсите, а не валидируйте.)

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

Вернемся к определению метода . Для типа там представлено также одно весьма низкоуровневое ограничение — . Оно говорит о том, что тип не обязательно должен быть типом с известным размером на этапе компиляции (что задано по умолчанию для обобщенных типов). Так как в метод передается только ссылка на этот тип, сам тип может быть и типом с неизвестным размером. Размеры типов принимаемых и возвращаемых значений у функций всегда должны быть известны на этапе компиляции, иначе будет невозможно зарезервировать для них место на стеке.

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

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

Легкий рефакторинг

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

Начало игрыПравить

Итак, вы приобрели Rust и вам уже не терпится сражаться и охотиться. Для начала желательно собрать компанию (3-5 человек — наиболее оптимально). Можно, конечно, выживать и одному. Всё зависит от вас. Но не стоит забывать, что Rust — онлайн игра, здесь вы не сможете в одиночку перевалить шестерых человек (разве что вы действительно невероятно круты). Да и выживать веселее в компании.

Следующий этап — поиск сервера. Тут тоже всё зависит от ваших предпочтений. Однако, советую сервера с 20-30 онлайном, больше — месиво. Также желательно выбирать сервер, открывшийся 1-2 дня назад — так есть возможность первыми получить всё самое лучшее.

Вы в игре. Как видите, с вами лишь обломок скалы, два бинта для остановки кровотечения да факел. Немного. Самым полезным из этого является именно обломок скалы. Факел можно сразу выкинуть. Нашим единственным инструментом мы можем добывать дерево из деревянных кучек, камни из скал и убивать животных. И тут мы сталкиваемся с неожиданной проблемой. Если вы — новичок, то, скорее всего, ориентироваться будет невозможно — карты в Rust нет. Остаётся только надеяться на более опытных товарищей. Тактика такая: все собирают ресурсы, лутают радиоактивные города, а затем — встречаются на условленном месте. Главное на этом этапе — собрать побольше дерева и еды.

Я хочу жить!

Делая обзор Rust, хочется рассказать про основные опасности. В игре вы столкнетесь с двумя основными опасностями — это окружающая среда и люди.

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

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

Бесплатные абстракции

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

И сравните с тем, как то же самое поведение реализуется в Rust:

Где вам понятнее, что происходит и где, по-вашему, вероятность ошибиться меньше? Мне кажется, что ответ очевиден.

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

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

Несмотря на то, что оба значения и имеют одинаковое числовое представление, они являются объектами разных типов, и поэтому перепутать и подставить одно значение вместо другого не получится. Этот паттерн называется «Новый тип» (New type), и он совершенно бесплатен в использовании. (Подробнее о преимуществах использования паттерна «Новый тип» вы можете прочитать в замечательной статье Передача намерений.)

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

Сложность изучения

Долгий процесс изучения Rust во многом следует из предыдущего раздела. Перед тем как написать вообще хоть что-то придется потратить время на освоение ключевой концепции владения памятью, т.к. она пронизывает каждую строчку. К примеру, простейший список у меня занял пару вечеров, в то время как на Kotlin то же самое пишется за 10 минут, при том что это не мой рабочий язык. Помимо этого многие привычные подходы к написанию алгоритмов или структур данных в Rust будут выглядеть по другому или вообще не сработают. Т.е. при переходе на него понадобится более глубокая перестройка мышления, просто освоить синтаксис будет недостаточно. Это далеко не JavaScript, который все проглотит и все стерпит. Думаю, Rust никогда не станет тем языком, на котором учат детей в школе программирования. Даже у С/С++ в этом смысле больше шансов.

В итоге

Мне показалась очень интересной идея управления памятью на этапе компиляции. В С/С++ у меня опыта нет, поэтому не буду сравнивать со smart pointer. Синтаксис в целом приятный и нет ничего лишнего. Я покритиковал Rust за сложность реализации графовых структур данных, но, подозреваю, что это особенность всех языков программирования без GC. Может быть, сравнения с Kotlin было и не совсем честным.

Почитать

Если вас заинтересовал Rust, то вот несколько ссылок:

  • Programming Rust: Fast, Safe Systems Development — хорошая книга, есть так же в электронном варианте
  • Rust Documentation — официальная документация, есть примеры
  • Idiomatic Rust code — список статей
  • ruRust/easy и ruRust/general — каналы в Gitter
  • r/rust/ — Reddit