что лучше rust или майнкрафт
Чего не стоит делать в Rust, если начали играть в 2021 году
Rust – это необычный симулятор выживания, который привлек к себе внимание огромное количество геймеров. При этом новички часто думают, что в этом проекте нет ничего сложного, и уже с самого начала делают все то, что и в других играх с элементами выживания.
К сожалению, Rust не отличается особым гостеприимством по отношению к новым игрокам, поэтому стартовать бывает довольно сложно. Перед вами подборка главных ошибок, которые делают новички, решившие поиграть в Rust в 2021 году.
Одному будет тяжело
Rust – далеко не самая лучшая многопользовательская игра для одного человека. Здесь есть несколько этапов развития, и добраться до каждого из них можно только за счет продолжительного гринда. Если играть в команде со своими друзьями, то вы гораздо быстрее достигните цели, чем в одиночку.
Также стоит отметить, что 99% других игроков не дадут вам мирно существовать в виртуальном мире игры. Вам постоянно придется отбиваться от обезумивших «дикарей», которые захотят отобрать ваши вещи и ресурсы. Естественно, ни у одного новичка не получится защитить себя от оравы более опытных игроков, поэтому лучше изначально залетать в Rust хотя бы с парой друзей.
Никому нельзя верить
Этот пункт частично противоречит предыдущему, но при этом он еще более важен. Прежде всего вам стоит забыть о том, что взаимодействие с другими игроками в многопользовательских проектах – это норма. Rust вообще не та игра, где нужно объединяться с незнакомыми людьми, чтобы вместе получить больше лута или ресурсов. Здесь вы можете рассчитывать только на себя, и если начнете доверять первому встречному игроку, то очень скоро поймете, почему этого нельзя делать. Особенно это касается товарищей с хорошей экипировкой, которых вы встретите на своем пути.
Дело в том, что в Rust каждый играет сам за себя, а опытные игроки очень часто обманывают новичков самыми разными способами. Незнакомец, который предложит побегать с ним по виртуальному миру и при этом будет носить броню заметно лучше вашей, скорей всего грифер. Это такой игрок, который при первой же удобной возможности просто вас убьет и заберет все вещи. Так что, начиная играть в Rust, никому не доверяйте!
Курс юного строителя
Если вы вдруг не знали, то в Rust есть строительство, и здесь оно играет довольно важную роль. При этом данная механика имеет ряд особенностей, которые придется изучить в самом начале знакомства с игрой, иначе ваши архитектурные «шедевры» будут попросту разваливаться, а вы впустую потратите ценные ресурсы.
Прежде всего стоит отметить, что у каждого строительного блока есть мягкая и твердая сторона. Во время строительства блок всегда нужно устанавливать таким образом, чтобы твердая сторона находилась снаружи будущего здания. Если не соблюдать это правило и размещать материалы как попало, то вашу постройку сможет развалить первый попавшийся игрок, причем с помощью обычного топора или кирки. Согласитесь, будет не очень приятно наблюдать за тем, как несколько часов ваших трудов кто-то разбирает по кирпичикам за считаные минуты.
Все вещи в одном месте
Огромное количество игроков в Rust вообще не уделяют время крафту. Они считают, что гораздо проще украсть готовые предметы у других пользователей, чем стоять у станка и пытаться что-то сделать. Именно поэтому в этой игре противопоказано хранить все свои вещи в одном месте.
Ни в коем случае не размещайте абсолютно все запасы на единственной базе, да еще и в конкретном помещении. В таком случае после случайного налета кучки любителей халявы вы потеряете абсолютно все. Конечно, вряд ли у новичка хватит ресурсов, чтобы построить себе 4-5 домов и правильно распределить по ним ценные предметы, но хотя бы попробуйте сделать что-то подобное. Неплохим решением будет на территории одной базы построить несколько «нычек» и распределить по ним ресурсы и предметы.
Не забывайте про аптечки
Если вы решите, что аптечки вам не нужны и со своим крутым автоматом вы сможете одолеть кого угодно, то Rust очень быстро вас разочарует. Здесь очень просто погибнуть, и иногда вы даже не будете понимать, почему это вообще произошло. В результате игрок, у которого было полно аптечек, просто завалит вас рандомной палкой и заберет тот самый крутой автомат.
Поставьте себе домофон
Если же вы не можете сделать кодовый замок или уже поставили везде обычные двери, то не делайте ключ. Пускай доступ к зданию будет только у вас. Отсутствие ключа гарантировано защитит ваши владения, даже если вы внезапно погибнете.
Не используйте факел
Дело в том, что свет от факела моментально привлечет к вам внимание других игроков. Часть из них будет гриферами, которые быстро прибегут на ваш «сигнал» и просто убьют. На этом ваш многообещающий забег в Rust просто закончится и придется начинать все сначала. Первое время лучше бегайте без факела и пытайтесь ориентироваться на карте с помощью своего зрения.
Вы всегда в опасности
Многие новички ошибочно думают, что после того, как они построят себе укрытие и обзаведутся хоть какой-то экипировкой, можно просто расслабиться и наслаждаться игровым процессом Rust. Этот проект не об этом, вы всегда будете под прицелом у других игроков! Причем если у вас вдруг все слишком хорошо и на это обратят внимание остальные пользователи игровой сессии, то очень скоро вас ждет набег незваных гостей.
Перестрелка – не самая лучшая идея
Некоторые новички в Rust почему-то считают, что это экшен-шутер, в котором прямо-таки необходимо ввязываться в перестрелки и каждую минуту показывать, кто здесь круче. На самом деле проект про выживание, и я вам гарантирую, что ваша беготня с автоматом закончится очень быстро, если вы вдруг решите, что можете держать всю карту в страхе.
Вот такие советы мы решили дать новичкам, которые только надумали залететь в Rust! Делая все эти вещи, вы гарантировано проживете в виртуальном мире игры чуточку дольше и при этом гораздо лучше узнаете все тонкости проекта. Главное, не забывайте всегда быть начеку, здесь нет зоны комфорта.
Попробуем выдвинуть аргументы против Rust
Недавно я прочитал статью c критикой Rust. Хотя в ней было много правильных вещей, она мне не понравилась — слишком многое там очень спорно. В целом, я вообще не могу рекомендовать к прочтению никакой статьи с критикой Rust. Это нехорошо, ведь важно обсуждать недостатки, а шельмование низкокачественной и неумелой критики, к сожалению, заставляет пропустить мимо внимания действительно хорошие аргументы.
Итак, попробую привести аргументы против Rust.
Не всё программирование является системным
Rust — язык системного программирования. Он обеспечивает точный контроль над компоновкой данных и поведением кода во время выполнения, обеспечивая максимальную производительность и гибкость. В отличие от других языков системного программирования, он также обеспечивает безопасность памяти — глючные программы завершаются чётко определенным образом, не допуская (потенциально опасного) неопределённого поведения.
Однако в большинстве случаев не требуется абсолютная производительность или контроль над аппаратными ресурсами. Для этих ситуаций современные управляемые языки, такие как Kotlin или Go, обеспечивают приличную скорость, завидную производительность и безопасность памяти благодаря использованию сборщика мусора с динамическим управлением памятью.
Сложность
Время программиста дорого стоит, а в случае Rust придётся потратить много времени на изучение самого языка. Сообщество приложило немало сил для создания высококачественных учебных материалов, но язык очень большой. Даже если вам выгодно переписать проект на Rust, само изучение языка может оказаться слишком дорогим.
Цена за улучшенный контроль — это проклятие выбора:
В Kotlin вы пишете класс Foo(val bar: Bar) и приступаете к решению задачи. В Rust нужно сделать выбор, иногда достаточно важный, со специальным синтаксисом.
Вся эта сложность не просто так — мы не знаем, как создать более простой и безопасный для памяти язык низкого уровня. Но не для каждой задачи нужен низкоуровневый язык.
Время компиляции
Время компиляции — это универсальный фактор. Если программа на каком-то языке медленно запускается, но этот язык позволяет быструю компиляцию, то у программиста будет больше времени для оптимизации, чтобы ускорить запуск программы!
В дилемме дженериков Rust намеренно выбрал медленные компиляторы. В этом есть определённый смысл (рантайм действительно ускоряется), но вам придётся всеми силами бороться за разумное время сборки в более крупных проектах.
rustc реализует, вероятно, самый продвинутый алгоритм инкрементной компиляции в продакшн-компиляторах, но это немного похоже на борьбу с встроенной в язык моделью компиляции.
В отличие от C++, сборка Rust не распараллеливается до предела, количество параллельных процессов ограничено длиной критического пути в графе зависимостей. Разница будет заметна, если у вас больше 40 ядер для компиляции.
В Rust также нет аналогов для идиомы pimpl, так что изменение крейта требует перекомпиляции (а не просто перелинковки) всех его обратных зависимостей.
Зрелость
Пять лет — это определённо малый срок, так что Rust молодой язык. Хотя будущее кажется блестящим, но всё-таки более вероятно, что через десять лет мы будем программировать на С, а не на Rust (см. эффект Линди). Если вы пишете софт на десятилетия, то должны серьёзно рассмотреть риски, связанные с выбором новых технологий (хотя выбор Java вместо Cobol для банковского программного обеспечения в 90-е годы ретроспективно оказался правильным выбором).
Есть только одна полная реализация Rust — компилятор rustc. Наиболее продвинутая альтернативная реализация mrustc целенаправленно пропускает многие статические проверки безопасности. На данный момент rustc поддерживает только один продакшн-ready бэкенд — LLVM. Следовательно, поддержка процессорных архитектур здесь более узкая, чем у C, у которого есть реализация GCC, а также поддержка ряда проприетарных компиляторов, специфичных для конкретных вендоров.
Наконец, у Rust нет официальной спецификации. Текущая спецификация не завершена и не документирует некоторые мелкие детали реализации.
Альтернативы
Кроме Rust, для системного программирования есть и другие языки, в том числе C, C++ и Ada.
Современный C++ предоставляет инструменты и рекомендации для повышения безопасности. Есть даже предложение о безопасности времени жизни объектов в стиле Rust! В отличие от Rust, использование этих инструментов не гарантирует отсутствие проблем с безопасностью памяти. Но если вы уже поддерживаете большой объём кода C++, имеет смысл проверить, возможно, следование рекомендациям и использование санитайзеров поможет в решении проблем безопасности. Это трудно, но явно легче, чем переписывать весь код на другом языке!
Если вы используете C, то можете применить формальные методы, чтобы доказать отсутствие неопределённого поведения, или просто тщательно всё протестировать.
Ada безопасна для памяти, если не использовать динамическую память (никогда не вызывайте free ).
Rust — интересный язык по соотношению затрат к безопасности, но далеко не единственный!
Набор инструментов
Инструменты Rust не назовёшь идеальными. Базовый инструментарий, компилятор и система сборки (cargo) часто называют лучшими в своём классе.
Но, например, некоторые инструменты, связанные со средой выполнения (в первую очередь, для профилирования кучи), просто отсутствуют — трудно размышлять о рантайме, если инструмента просто нет! Кроме того, поддержка IDE тоже далеко не соответствует уровню надёжности Java. На Rust просто невозможен автоматизированный сложный рефакторинг программы с миллионами строк.
Интеграция
Что бы ни обещал Rust, но сегодняшний мир системного программирования говорит на C и C++. Rust намеренно не пытается имитировать эти языки — он не использует классы в стиле C++ или C ABI.
Это означает, что между мирами нужно наводить мосты. Интеграция не будет бесшовной, она небезопасна, не всегда обойдётся без затрат и требует синхронизации между языками. В то время как местами интеграция работает и инструментарий сближается, на этом пути возникают случайные препятствия из-за общей сложности.
Одна из специфических проблем заключается в том, что самоуверенное мировоззрение Cargo (великолепное для чистых проектов Rust) может затруднить интеграцию с более крупными системами сборки.
Производительность
«Использование LLVM» не является универсальным решением всех проблем производительности. Хотя я не знаю бенчмарков, сравнивающих производительность C++ и Rust в целом, но нетрудно придумать задачи, где Rust уступает C++.
Вероятно, самая большая проблема в том, что семантика перемещения Rust основана на значениях ( memcpy на уровне машинного кода). С другой стороны, семантика C++ использует специальные ссылки, из которых можно взять данные (указатели на уровне машинного кода). Теоретически, компилятор должен видеть цепочку копий, на практике это часто не так: #57077. Связанная с этим проблема заключается в отсутствии размещения новых данных — Rust иногда нужно копировать байты в/из стека, в то время как C++ может создать объект на месте.
Несколько забавно, что в дефолтный Rust ABI (в котором пожертвовали стабильностью ради эффективности) иногда работает хуже, чем у C: #26494.
Наконец, хотя теоретически код Rust должен быть более эффективным из-за значительно более богатой информации об алиасах, включение оптимизации, связанной с алиасами, вызывает ошибки LLVM и некорректную компиляцию: #54878.
Потенциально более серьёзная проблема заключается в том, что Rust с его определениями дженериков менее выразителен, чем C++. Таким образом, некоторые шаблонные трюки C++ для высокой производительности не могут быть выражены в Rust с помощью хорошего синтаксиса.
Значение unsafe
Возможно, идея unsafe даже более важна для Rust, чем владение и заимствование. Выделяя все опасные операции в блоки unsafe и функции и настаивая на предоставлении им безопасного интерфейса более высокого уровня, можно создать систему, которая одновременно:
Но теоретические перспективы не столь радужны.
Во-первых, нет определения модели памяти Rust, поэтому невозможно формально проверить, является ли данный небезопасный блок допустимым или нет. Существует неофициальное определение «вещей, которые rustc делает или на которые может полагаться» и продолжается работа над верификатором рантайма, но фактическая модель не ясна. Таким образом, где-то может быть какой-то небезопасный код, который сегодня работает нормально, а завтра будет объявлен недействительным и сломается в новой оптимизации компилятора через год.
Во-вторых, есть мнение, что блоки unsafe на самом деле не являются модульными. Достаточно мощные блоки unsafe могут, по сути, расширить язык. Два таких расширения не делают ничего плохого в изоляции друг от друга, но приводят к неопределённому поведению при одновременном использовании: см. статью «Наблюдаемая эквивалентность и небезопасный код».
Вот некоторые темы, которые я намеренно опустил:
10 неочевидных преимуществ использования Rust
Rust — это молодой и амбициозный язык для системного программирования. В нем реализовано автоматическое управление памятью без сборщика мусора и прочих накладных расходов времени исполнения. Кроме этого, в языке Rust используется семантика перемещения по умолчанию, имеются беспрецендентные правила обращения к изменяемым данным, а также учитываются времена жизни ссылок. Это позволяет ему гарантировать безопасность памяти и облегчает многопоточное программирование, ввиду отсутствия гонок данных.
Все это уже хорошо известно всем, кто хоть немного следит за развитием современных технологий программирования. Но что если вы не системный программист, да и многопоточного кода в ваших проектах не много, но вас все же привлекает производительность Rust’а. Получите ли вы какие-то дополнительные преимущества от его использования в прикладных задачах? Или все, что он вам даст дополнительно — это суровую борьбу с компилятором, который будет заставлять вас писать программу так, чтобы она неотступно следовала правилам языка по заимствованию и владению?
В данной статье собран десяток неочевидных и особо не рекламируемых преимуществ использования Rust, которые, я надеюсь, помогут вам определиться с выбором этого языка для ваших проектов.
1. Универсальность языка
Несмотря на то, что Rust позиционируется как язык для системного программирования, он подходит и для решения высокоуровневых прикладных задач. Вам не придется работать с сырыми указателями, если для вашей задачи это не нужно. В стандартной библиотеке языка уже реализовано большинство типов и функций, которые могут понадобиться в прикладной разработке. Также можно легко подключать внешние библиотеки и использовать их. Система типов и обобщенное программирование в Rust позволяют использовать абстракции достаточно высокого уровня, хотя прямая поддержка ООП в языке отсутствует.
Давайте рассмотрим несколько простых примеров использования Rust.
Пример совмещения двух итераторов в один итератор по парам элементов:
Пример использования внешней библиотеки regex для работы с регулярными выражениями:
Пример использования обобщенного типа в структуре:
На Rust вы можете писать эффективные системные утилиты, большие настольные приложения, микросервисы, веб-приложения (включая клиентскую часть, так как Rust можно скомпилировать в Wasm), мобильные приложения (хотя в этом направлении экосистема языка пока развита слабо). Такая универсальность может оказаться преимуществом для многопроектных команд, потому что она позволяет использовать одинаковые подходы и одни и те же модули во множестве разных проектов. Если вы привыкли к тому, что каждый инструмент предназначен для своей узкой области применения, то попробуйте посмотреть на Rust как на ящик с инструментами одинаковой надежности и удобства. Возможно, вам именно этого и не хватало.
2. Удобные инструменты сборки и управления зависимостями
Это явно не рекламируется, но многие замечают, что в Rust реализована одна из лучших на сегодняшний день система сборки и управления зависимостями. Если вы программировали на С или С++, и вопрос безболезненного использования внешних библиотек стоял для вас достаточно остро, то использование Rust с его инструментом сборки и менеджером зависимостей Cargo будет хорошим выбором для ваших новых проектов.
Кроме того, что Cargo будет за вас загружать зависимости и управлять их версиями, собирать и запускать ваши приложения, выполнять тесты и генерировать документацию, дополнительно он может быть расширен плагинами и для других полезных функций. Например, существуют расширения, позволяющие Cargo определять устаревшие зависимости вашего проекта, производить статический анализ исходного кода, собирать и редеплоить клиентские части веб-приложений и многое другое.
Конфигурационный файл Cargo использует для описания настроек проекта дружелюбный и минималистичный язык разметки toml. Вот пример типичного файла конфигурации Cargo.toml :
А ниже приведены три типичные команды использования Cargo:
С их помощью будет произведена проверка исходного кода на ошибки компиляции, сборка проекта и запуск тестов, сборка и запуск программы на выполнение, соответственно.
3. Встроенные тесты
Модульные тесты в Rust писать настолько легко и просто, что хочется это делать снова и снова. 🙂 Зачастую вам будет проще написать модульный тест, чем пытаться протестировать функциональность другим способом. Вот пример функций и тестов к ним:
Особого внимания заслуживают исполняемые как тесты примеры документации, но об этом будет сказано ниже.
Встроенные тесты производительности (бенчмарки) тоже имеются, но они пока не стабилизированы, поэтому доступны только в ночных сборках компилятора. В стабильном Rust для этого вида тестирования придется использовать внешние библиотеки.
4. Хорошая документация с актуальными примерами
Стандартная библиотека Rust очень хорошо документирована. Html-документация генерируется автоматически по исходному коду с markdown-описаниями в док-комментариях. Более того, док-комментарии в коде на Rust содержат примеры кода, которые исполняются во время запуска тестов. Этим гарантируется актуальность примеров:
Здесь пример использования метода as_bytes у типа String
будет выполнен как тест во время запуска тестов.
Кроме этого, для Rust-библиотек распространена практика создания примеров их использования в виде небольших самостоятельных программ, расположенных в директории examples в корне проекта. Эти примеры также являются важной частью документации и они также компилируются и выполняются во время прогона тестов, но их можно запускать и независимо от тестов.
5. Умное автовыведение типов
В программе на Rust можно явно не указывать тип выражения, если компилятор в состоянии его вывести автоматически, исходя из контекста использования. Причем это касается не только тех мест, где объявляются переменные. Давайте рассмотрим такой пример:
Если мы расставим аннотации типов, то данный пример будет выглядеть так:
Такая система автовыведения типов избавляет код от лишнего шума и делает его таким же лаконичным, как и код на каком-нибудь динамически типизированном языке программирования. И это при сохранении строгой статической типизации!
Конечно, мы не можем польностью избавиться от указания типов в статически типизированном языке. В программе должны быть точки, в которых типы объектов гарантированно известны, чтобы в других местах можно было эти типы выводить. Такими точками в Rust являются объявления пользовательских типов данных и сигнатуры функций, в которых нельзя не указывать используемые типы. Но в них можно вводить «метапеременные типов», при использовании обобщенного программирования.
6. Сопоставление с образцом в местах объявления переменных
на самом деле не ограничивается только объявлением новых переменных. То, что она делает на самом деле — это осуществляет сопоставление выражения справа от знака равенства с образцом слева. А новые переменные могут быть введены в составе образца (и только так). Взгляните на следующий пример, и вам станет понятнее:
Другой популярный пример использования образца в цикле for :
Или при сопоставлении в операторе match :
Сопоставление с образцом делает код весьма компактным и выразительным, а в операторе match оно вообще незаменимо. Оператор match — это оператор полного вариативного анализа, поэтому случайно забыть в нем проверить какое-то из возможных совпадений для анализируемого выражения у вас не получится.
7. Расширение синтаксиса и пользовательские DSL
Синтаксис языка Rust ограничен, во многом из-за сложности используемой в языке системы типов. Например, в Rust отсутствуют именованные аргументы функций и функции с переменным числом аргументов. Но можно обойти эти и другие ограничения с помощью макросов. В Rust существует два вида макросов: декларативные и процедурные. С декларативными макросами у вас никогда не будет таких же проблем, как с макросами в С, потому что они гигиеничны и работают не на уровне текстовой замены, а на уровне замены в абстрактном синтаксическом дереве. Макросы позволяют создавать абстракции на уровне синтаксиса языка. Например:
Помимо того, что данный макрос расширияет синтаксические возможности вызова «функции» печати форматированной строки, он еще будет в своей реализации проверять соответствие входных аргументов указанной строке формата во время компиляции, а не во время выполнения. С помощью макросов вы можете вводить лаконичный синтаксис под ваши собственные проектные нужды, создавать и использовать DSL. Вот пример использования кода на JavaScript внутри Rust-программы, компилирующейся в Wasm:
Макрос js! определен в пакете stdweb и он позволяет встраивать полноценный JavaScript-код в вашу программу (за исключением строк в одинарных кавычках и операторов, не завершенных точкой с запятой) и использовать в нем объекты из Rust-кода с помощью синтаксиса @
Макросы открывают огромные возможности по адаптации синтаксиса Rust-программ к специфическим задачам конкретной предметной области. Они сэкономят ваше время и внимание при разработке сложных приложений. Не за счет увеличения накладных расходов времени выполнения, но за счет увеличения времени компиляции. 🙂
8. Автогенерация зависимого кода
Процедурные derive-макросы в Rust широко используются для автоматической реализации типажей и прочей кодогенерации. Вот пример:
Здесь с помощью derive-макросов Serialize и Deserialize из библиотеки serde для структуры Point автоматически генерируются методы ее сериализации и десериализации. Дальше можно передавать экземпляр этой структуры в различные функции сериализации, например, преобразующие его в JSON строку.
9. Алгебраический тип данных
Алгебраический тип данных, говоря упрощенно — это составной тип данных, являющийся объединением структур. Более формально — это тип-сумма из типов-произведений. В Rust такой тип определяется с помощью ключевого слова enum :
Выяснить, какой действительно тип приняло значение в конкретном случае можно с помощью сопоставления с образцом:
В Rust отсутствует null-значение, ровно как и досадные ошибки непредвиденного обращения к нему. Вместо этого там, где действительно необходимо указать возможность отсутствия значения, используется Option :
Алгебраический тип данных — достаточно мощный и выразительный инструмент, который открывает дверь в Type-Driven Development. Грамотно написанная программа в этой парадигме возлагает на систему типов большую часть проверок корректности своей работы. Поэтому если вам нехватает немного Haskell в повседневном промышленном программировании, Rust может стать вашей отдушиной. 🙂
10. Легкий рефакторинг
Развитая строгая статическая система типов в Rust и попытка выполнить как можно больше проверок во время компиляции, приводит к тому, что дорабатывать и рефакторить код становится доcтаточно просто и безопасно. Если после изменений программа собралась, то это значит, что в ней остались только логические ошибки, не связанные с тем функционалом, проверка которого была возложена на компилятор. В сочетании с легкостью добавления модульных тестов для проверки логики, это приводит к серьезным гарантиям надежности программ и росту уверенности программиста в корректной работе своего кода после внесения изменений.
Пожалуй это все, о чем я хотел рассказать в этой статье. Конечно, у Rust есть еще много других достоинств, а также имеется ряд недостатков (некоторая сырость языка, отсутствие привычных идиом программирования, «нелитературный» синтаксис), о которых здесь не упоминается. Если вам есть, что о них рассказать — напишите в комментариях. А вообще, опробуйте Rust на практике. И может быть его достоинства для вас перевесят все его недостатки, как это произошло в моем случае. И вы, наконец, получите именно тот набор инструментов, в котором долго нуждались.