что такое батики в раст
Чего не стоит делать в Rust, если начали играть в 2021 году
Rust – это необычный симулятор выживания, который привлек к себе внимание огромное количество геймеров. При этом новички часто думают, что в этом проекте нет ничего сложного, и уже с самого начала делают все то, что и в других играх с элементами выживания.
К сожалению, Rust не отличается особым гостеприимством по отношению к новым игрокам, поэтому стартовать бывает довольно сложно. Перед вами подборка главных ошибок, которые делают новички, решившие поиграть в Rust в 2021 году.
Одному будет тяжело
Rust – далеко не самая лучшая многопользовательская игра для одного человека. Здесь есть несколько этапов развития, и добраться до каждого из них можно только за счет продолжительного гринда. Если играть в команде со своими друзьями, то вы гораздо быстрее достигните цели, чем в одиночку.
Также стоит отметить, что 99% других игроков не дадут вам мирно существовать в виртуальном мире игры. Вам постоянно придется отбиваться от обезумивших «дикарей», которые захотят отобрать ваши вещи и ресурсы. Естественно, ни у одного новичка не получится защитить себя от оравы более опытных игроков, поэтому лучше изначально залетать в Rust хотя бы с парой друзей.
Никому нельзя верить
Этот пункт частично противоречит предыдущему, но при этом он еще более важен. Прежде всего вам стоит забыть о том, что взаимодействие с другими игроками в многопользовательских проектах – это норма. Rust вообще не та игра, где нужно объединяться с незнакомыми людьми, чтобы вместе получить больше лута или ресурсов. Здесь вы можете рассчитывать только на себя, и если начнете доверять первому встречному игроку, то очень скоро поймете, почему этого нельзя делать. Особенно это касается товарищей с хорошей экипировкой, которых вы встретите на своем пути.
Дело в том, что в Rust каждый играет сам за себя, а опытные игроки очень часто обманывают новичков самыми разными способами. Незнакомец, который предложит побегать с ним по виртуальному миру и при этом будет носить броню заметно лучше вашей, скорей всего грифер. Это такой игрок, который при первой же удобной возможности просто вас убьет и заберет все вещи. Так что, начиная играть в Rust, никому не доверяйте!
Курс юного строителя
Если вы вдруг не знали, то в Rust есть строительство, и здесь оно играет довольно важную роль. При этом данная механика имеет ряд особенностей, которые придется изучить в самом начале знакомства с игрой, иначе ваши архитектурные «шедевры» будут попросту разваливаться, а вы впустую потратите ценные ресурсы.
Прежде всего стоит отметить, что у каждого строительного блока есть мягкая и твердая сторона. Во время строительства блок всегда нужно устанавливать таким образом, чтобы твердая сторона находилась снаружи будущего здания. Если не соблюдать это правило и размещать материалы как попало, то вашу постройку сможет развалить первый попавшийся игрок, причем с помощью обычного топора или кирки. Согласитесь, будет не очень приятно наблюдать за тем, как несколько часов ваших трудов кто-то разбирает по кирпичикам за считаные минуты.
Все вещи в одном месте
Огромное количество игроков в Rust вообще не уделяют время крафту. Они считают, что гораздо проще украсть готовые предметы у других пользователей, чем стоять у станка и пытаться что-то сделать. Именно поэтому в этой игре противопоказано хранить все свои вещи в одном месте.
Ни в коем случае не размещайте абсолютно все запасы на единственной базе, да еще и в конкретном помещении. В таком случае после случайного налета кучки любителей халявы вы потеряете абсолютно все. Конечно, вряд ли у новичка хватит ресурсов, чтобы построить себе 4-5 домов и правильно распределить по ним ценные предметы, но хотя бы попробуйте сделать что-то подобное. Неплохим решением будет на территории одной базы построить несколько «нычек» и распределить по ним ресурсы и предметы.
Не забывайте про аптечки
Если вы решите, что аптечки вам не нужны и со своим крутым автоматом вы сможете одолеть кого угодно, то Rust очень быстро вас разочарует. Здесь очень просто погибнуть, и иногда вы даже не будете понимать, почему это вообще произошло. В результате игрок, у которого было полно аптечек, просто завалит вас рандомной палкой и заберет тот самый крутой автомат.
Поставьте себе домофон
Если же вы не можете сделать кодовый замок или уже поставили везде обычные двери, то не делайте ключ. Пускай доступ к зданию будет только у вас. Отсутствие ключа гарантировано защитит ваши владения, даже если вы внезапно погибнете.
Не используйте факел
Дело в том, что свет от факела моментально привлечет к вам внимание других игроков. Часть из них будет гриферами, которые быстро прибегут на ваш «сигнал» и просто убьют. На этом ваш многообещающий забег в Rust просто закончится и придется начинать все сначала. Первое время лучше бегайте без факела и пытайтесь ориентироваться на карте с помощью своего зрения.
Вы всегда в опасности
Многие новички ошибочно думают, что после того, как они построят себе укрытие и обзаведутся хоть какой-то экипировкой, можно просто расслабиться и наслаждаться игровым процессом Rust. Этот проект не об этом, вы всегда будете под прицелом у других игроков! Причем если у вас вдруг все слишком хорошо и на это обратят внимание остальные пользователи игровой сессии, то очень скоро вас ждет набег незваных гостей.
Перестрелка – не самая лучшая идея
Некоторые новички в Rust почему-то считают, что это экшен-шутер, в котором прямо-таки необходимо ввязываться в перестрелки и каждую минуту показывать, кто здесь круче. На самом деле проект про выживание, и я вам гарантирую, что ваша беготня с автоматом закончится очень быстро, если вы вдруг решите, что можете держать всю карту в страхе.
Вот такие советы мы решили дать новичкам, которые только надумали залететь в Rust! Делая все эти вещи, вы гарантировано проживете в виртуальном мире игры чуточку дольше и при этом гораздо лучше узнаете все тонкости проекта. Главное, не забывайте всегда быть начеку, здесь нет зоны комфорта.
Так ли токсичен синтаксис Rust?
Синтаксис — это первое, на что обращают внимание разработчики, впервые столкнувшись с кодом на Rust. И сложно найти что-то другое, что вызывало бы больше негодования у новичков, и при этом такое, к чему совершенно спокойно относятся уже «понюхавшие пороха» Rust-программисты. Посмотрите, например, сколько эмоций вызывает синтаксис Rust у комментаторов одной из типичных новостей про Rust на OpenNET:
Язык не для меня, нахожу его немного убогим в плане синтаксиса, но мне нравится много идей тем, взять хотя бы управление памятью.
Вообще никак не зашел. Зашел он хейтерам крестов, может их кресты както в детстве обидели, я не знаю. Но когда даже крестовики говорят, что синтаксис конченый — это очень серьезный повод задуматься, крестов видели конченый синтаксис.
Самое интересное в этой истории то, что примеры на сайте раста типа «привет мир», почему то намного более читабельные чем то, что реально используется и лежит на гитхабе, а там адовый синтаксис с кучей спец символов и какая то ещё жуткая муть.
Сначала хотел указать вам на ошибку, но потом понял, что «синтоксис» в данном контексте, нужно понимать как «токсичный синтаксис». Вот это именно то, чего мне не хватало что бы охарактеризовать раст.
Теперь я точно знаю: Раст — это ЯП с ТОКСИЧНЫМ СИНТАКСИСОМ!
Новый язык должен бить старые по всем параметрам уверенно и с превосходством. Rust это не новая технология, это всё что было раньше только с инфраструктурой и плохо читаемым синтаксисом.
Да вообще не понимаю, чего все ополчились на Rust. Нормальный язык с нормальным синтаксисом, надо просто мозгом думать.
Синтаксис языка, безусловно, имеет значение. Но сама величина его важности условна: для людей, только начинающих знакомство с языком, она больше. А вот для разработчиков, уже привыкших к языку, гораздо важнее выразительность: то есть не синтаксис сам по себе в абстрактном отношении с собой, а то, насколько язык хорошо справляется с выражением требуемых концепций и насколько они легко воспринимаются.
Как ни печально, но встречают язык по голому синтаксису. И синтаксис Rust тут не предлагает чего-то революционного. По сути это обычный C-подобный синтаксис, который имеют и многие другие языки, но с различными современными улучшениями. И они, как ни странно, делают его лучше других, в ряде случаев выразительнее.
Проблема в том, что сразу этого не видно. Чтобы оценить выразительность, нужно уже владеть теми концепциями, языковую запись которых требуется оценить. Более того, нужно также иметь представление о проблемах выражения подобных концепций в других языках и иметь в виду возможное наличие этих проблем при оценке. Поэтому концептуальная сложность самого языка может выглядеть на первый взгляд как переусложнение синтаксиса. На самом деле синтаксис Rust не так плох, как о нем говорят. Он имеет множество замечательных находок, которыми удобно пользоваться, и вопреки расхожему мнению, читаются программы на Rust достаточно хорошо.
Так давайте рассмотрим подробнее, чем именно синтаксис Rust не угодил своим критикам и почему никто из них не может предложить ничего лучше, когда их просят это сделать.
Фигурные скобки
Многие люди, которые критикуют языки программирования за использование фигурных скобок и других спецсимволов, часто хотят, чтобы программа выглядела подобно тексту на естественном языке. Но такой формат совершенно неудобен для записи программного кода, и вот почему: текст на естественном языке — это просто запись последовательного потока речи, тогда как текст программы — это чаще выражение структурных отношений между элементами кода. Очевидно, что графическими обозначениями зачастую можно добиться большей выразительности при записи структур, будь то спецсимволы или отступы.
Выделение блоков кода одними лишь отступами имеет свои преимущества, но чаще всего оно требует серьезной дисциплины от программиста. Особенно при перемещении фрагментов кода из одного места в другое, из одного уровня вложенности — в другой. Читаемость кода улучшается, но возрастает напряжение внимания при работе с кодом, возникает почва для появления досадных ошибок.
Интересно, что одна из главных проблем, которую должно было решить обозначение блоков отступами — это проблема несоответствия между отступами и реальной синтаксической группировкой инструкций. При этом человек прочитывает один порядок, ориентируясь на отступы, а парсер языка — другой порядок, ориентируясь на правило, по которому блок выделяется скобками:
Должен ли bar вызываться только тогда, когда test завершился успешно? По правилам языка — нет, так как отсутствуют фигурные скобки, обозначающие блок. Но такое форматирование кода затрудняет для человека понимание этого. Все потому, что имеется лишняя избыточность в обозначении блоков: скобками — для парсера, отступами — для человека, и они могут не совпадать. Поэтому в Python решили отказаться от этой избыточности:
Но ирония в том, что такое единообразное представление, тем не менее, не устраняет возможную ошибку полностью. То есть, ошибкой в первом примере могло быть не отсутствие фигурных скобок, а наличие случайного отступа. Тогда программа в первом случае работает правильно, но выглядит некорректной, тогда как во втором случае она действительно некорректна.
Появление подобных ошибок наталкивает на мысль, что избыточность в обозначении блоков — не так уж и плоха, она позволяет ввести дополнительные способы контроля. Более того, наличие скобок делает возможным автоматическое расставление отступов: зная, что код был отформатирован автоматически (например, по сохранению изменений в файл), программист может визуально убедиться, что никакая инструкция не съехала, все расставлено на своих позициях как надо.
Даже без автоформатирования, Rust решает проблему случайных отступов радикальным образом:
Вообще, в Rust блоки очень важны, они являются самостоятельными программными объектами с особыми свойствами.
Во-первых, блоки обозначают область видимости переменных. По достижении конца блока все переменные, введенные внутри блока, уничтожаются и происходит освобождение занятых ими ресурсов:
Здесь data вне блока — это мьютекс, содержащий некоторые данные. А data внутри блока — это специальная ссылка на защищенные мьютексом данные, которая формируется при блокировке мьютекса и после удаления которой исходный мьютекс будет разблокирован. То есть блок в данном случае ограничивает область блокировки мьютекса, и встречаться подобные блоки могут в любом месте, где допустимо размещение инструкций.
Во-вторых, блоки сами являются выражениями и они возвращают значения! Значением блока считается значение последнего вычисленного выражения в блоке, если оно не завершается точкой с запятой. Иначе блок будет возвращать пустое значение.
Очень удобно вводить локальные блоки и компоновать их с выражениями вовне. Каким бы ни было простым это свойство блоков, оно кардинально меняет способ работы с потоком выполнения: оно позволяет не только локализовать связанные инструкции, но и приносит в поток вложенную структурность. Благодаря этому, в частности, становится ненужен специальный тернарный условный оператор (что также избавляет от ряда проблем, с ним связанных):
А многие функции и замыкания избавляются от лишнего синтаксиса с оператором возврата:
Сложно представить, как можно обеспечить удобную (и свободную от известных ошибок) поддержку блоков такой же функциональности, не используя скобок для их обозначения.
Точка с запятой
Отдельного рассмотрения заслуживает то, как работает точка с запятой в Rust. Многим не нравятся языки, которые требуют окончания инструкций точкой с запятой: зачем, если и так перевод строки может означать завершение инструкции? Отказ от точки с запятой оправдан, если в языке любая строка по умолчанию является инструкцией, как в Python. Если же язык использует концепцию «все есть выражение», которая расширяет его выразительные возможности, то без специального терминатора, по которому можно отличить строку-инструкцию от строки-выражения, удобным в использовании может быть только язык с динамической типизацией, такой как Ruby. Да и в таком языке в ряде случаев подход «все есть выражение» приводит к неудобствам:
Здесь foo возвращает число, но мы его не используем, а bar возвращает «пусто». Какого типа должно быть значение всего условного выражения?
Для динамически типизированных языков подобный код приемлем, потому что они допускают возврат значений разных типов в зависимости от условия, которое проверяется во время выполнения программы. Для статически типизированного языка — все плохо, придется ставить выражения-заглушки в каждой подобной ситуации, даже если результат никто не присваивает.
Rust решает эту проблему элегантным способом, который позволяет оставаться ему статически типизированным языком и при этом использовать преимущества подхода «все есть выражение» без особых хлопот. Способ заключается в том, что точка с запятой используется не как простой разделитель инструкций, а как оператор, превращающий выражение в инструкцию. При этом инструкция тоже интерпретируется как выражение, которое всегда возвращает пустое значение:
Здесь foo возвращает число, но мы его не используем, и нам не нужно, чтобы оно возвращалось из данной ветки условия, поэтому мы завершаем выражение точкой с запятой. При этом bar возвращает «пусто», а значит мы не обязаны ставить точку с запятой после его вызова (но можем и поставить, для симметрии), ибо типы возврата обеих веток уже совпали.
Интересно, что в совокупности с правилами владения и семантикой перемещения по умолчанию получается, что если объект был перемещен в выражение, которое закончилось точкой с запятой и объект никуда больше перемещен не был, то он удаляется:
В итоге подобное концептуальное расширение функций точки с запятой весьма удобно в использовании. В подавляющем большинстве случаев оно ни к каким проблемам не приводит, благодаря контролю соответствия типов и времен жизни со стороны компилятора. Что же касается «загрязнения» кода точками с запятой, то благодаря тому, что теперь она не ставится в ряде распространенных случаев, доля непустых строк в программах на Rust, содержащих точку с запятой, составляет около 20%. Не так уж и много.
Постфиксная запись типа
Иногда простота синтаксиса является кажущейся. Например, частенько задается вопрос: зачем в Rust используется постфиксная запись типа, ведь это усложняет запись, еще и приходится отделять имена от типа с помощью двоеточия, тогда как префиксная запись, принятая в C/C++, этого не требует и выглядит чище. На самом деле префиксная запись чище только в ряде простых случаев использования, но в более сложных случаях она изрядно запутывает и парсер, и человека.
Вообще, какой следует выбрать объективный критерий простоты синтаксиса? Мне видится, что немаловажно для оценки простоты синтаксиса является простота его парсера. Простой парсер может стать важным преимуществом языка программирования, так как он ускоряет время компиляции, разбор исходных файлов в IDE и утилитах анализа и автоматического преобразования кода (статические анализаторы, автоформатеры, авторефакторы и пр.). Кроме того, простота разбора синтаксиса также важна и для человека: во-первых, человеку тоже приходится «парсить» текст программы при ее чтении, а во-вторых, однозначность синтаксиса упрощает поиск нужных строк по кодовой базе, например, среди множества проектов на GitHub.
Рассмотрим пример объявления переменной определенного типа.
Первый вариант — это «паскалевский» способ декларации типа. Хотя он дополнительно «загрязнен» символом двоеточия, однако в общем случае он проще, как для разбора парсером, так и для восприятия человеком.
Для человеческого восприятия преимущества «паскалевской» записи раскрываются в сложных случаях:
Проблема данного кода в том, что в нем синтаксис типа перемешан с синтаксисом объявляемой переменной. Чтобы в этом разобраться, нужно применять особое правило чтения по спирали.
Синтаксис же, принятый в Rust, отделяет обозначение типа от имени переменной:
Ну и стоит сказать, что «паскалевская» декларация упрощается в случае автовыведения типа:
Тип просто не указывается. Отсутствует также и двоеточие, что улучшает читаемость кода в большинстве случаев использования переменных.
Для человека, читающего код, имя переменной зачастую важнее ее типа, поэтому логично, что оно ставится на первое место. К тому же имена обычно более равномерны по длине, чем типы, и постфиксная запись улучшает выравнивание полей и переменных в группе.
Зачем вообще нужен let?
Иногда let ругают за его избыточность и ненужность. Но обычно это делают те, кто не идет дальше элементарных примеров объявления переменных в Rust. Дело в том, что инструкция let занимается не объявлением переменных, а сопоставлением выражения с образцом (паттерном):
В простых случаях паттерн может состоять только из одного имени новой переменной, но это только частный случай. К тому же Rust не требует, чтобы значения с новыми именами были связаны именно в той же инструкции, где эти имена были объявлены:
Наконец, Rust поддерживает затенение переменных:
Сокращения в ключевых словах
Уж наверное только совсем ленивые критики синтаксиса Rust не «проехались» по сокращениям в ключевых словах. Зачастую дело выставляют таким образом, что Rust форсирует сокращения и сплошняком только из них и состоит. Но на самом деле из 40 ключевых слов языка сокращениями являются только 7:
При этом dyn и ref используются крайне редко. Дополнительно есть еще 5 коротких ключевых слов (в две и три буквы), которые не являются сокращениями:
На самом деле fn — весьма неплохой выбор для достижения компромисса между двумя крайностями. Наличие длинного слова function особо не помогает при чтении кода, обычно определение функции и так заметно по остальной ее сигнатуре и обязательному блоку с ее телом. Оно только оттягивает на себя внимание от имени и аргументов функции. К тому же оно занимает слишком много места, что плохо сказывается не только на скорости набора, но в ряде случаев и на чтении кода, например, в мессенджерах.
Но и отсутствие ключевого слова — тоже плохо, так как в этом случае возникают ситуации, когда становится невозможно однозначно отличить функциональный тип от других элементов языка:
Без специального ключевого слова, предваряющего имя объявляемой функции, становится сложно искать строки объявления по кодовой базе, так как они синтаксически становятся похожи на вызовы.
Стрелка в сигнатуре функций
Другая частая претензия к объявлению функций в Rust, это использование символа «стрелки» для разделения блока параметров от типа возвращаемого функцией результата:
Кажется, что последовательнее тут тоже использовать двоеточие:
Однако в таком случае создается впечатление, что тип bool относится ко всему выражению слева, то есть ко всей функции, тогда как на самом деле он является только частью функционального типа, указывающей тип возвращаемого значения. Проблему становится заметно еще отчетливее в случае объявления переменной функционального типа:
Видно, что двоеточие во втором случае затрудняет для человека восприятие типа, а стрелка подходит на его место лучше других символов, так как это принятое обозначение во многих функциональных языках, куда оно проникло из математики, из просто типизированного лямбда исчисления.
Без подобного сахара для такого тривиального случая, от обилия спецсимволов действительно начало бы пестрить в глазах.
Замыкания
В отличие от обычных функций, список аргументов у замыканий выделяется не круглыми скобками, а вертикальными чертами:
В большинстве случаев у замыкания типы аргументов и тип возвращаемого значения выводятся автоматически и указывать их не нужно. Если все тело замыкания состоит только из одного выражения, то фигурные скобки также можно не ставить.
Текущий синтаксис прошел определенную историю развития, в процессе которого он избавился от всего лишнего и приобрел эту окончательную, легковесную и лаконичную форму. Использование вертикальных черт улучшает читаемость кода: замыкание хорошо выделяется в группе аргументов функций, где оно используется чаще всего, при этом его сложно спутать с кортежем или массивом.
Преимущества подобного синтаксиса достаточно очевидны, остается загадкой, почему он не устраивает многих критиков языка. Пожалуй действительную проблему представляют только некоторые вырожденные случаи, например:
Конструкция |_| () выглядит некрасиво. Она означает, что мы игнорируем входной аргумент замыкания и возвращаем из замыкания пустое значение. Подобный map полезен, когда нужно преобразовать одно значение типа Result в другое с заменой положительного значения возврата на «пусто». Однако добиться этого можно и более наглядным образом, просто передав функцию drop вместо замыкания:
Двоеточия в пути
Такое происходит, когда разные типажи, реализованные для типа, используют одинаковые имена методов, и разрешить конфликт имен необходимо статическим указанием на конкретный типаж или тип, к которому относится метод. Подобная запись вызовов оказывается незаменимой при обобщенном программировании. Если использовать для статического и динамического обращения один и тот же разделитель, то становится невозможно различать подобные вызовы:
Программа, написанная на Rust, не имеет информации о типах во время выполнения, так как при компиляции происходит «стирание типов». Поэтому возникает необходимость различать динамические и статические обращения: механизм их работы сильно отличается, как и последствия, к которым они приводят, так что программист должен видеть это в коде явно. Однако в качестве утешения появляется возможность использовать одни и те же идентификаторы для имен переменных и модулей, что очень удобно.
Дженерики
Угловые скобки используются для работы с обобщенными типами как в контексте объявления обобщенных элементов, так и в контексте выражения при подстановке уже конкретных типов. И с этим связана, пожалуй, самая уродливая конструкция языка — жуткий мутант, внушающий первобытный страх любому, кто случайно заглядывает в глубины «синтОксиса» кода на Rust. Имя этому монстру — Турбофиш:
Времена жизни
Идентификаторы с префиксом или суффиксом в виде одинарной кавычки используются в языках семейства ML. В частности, в OCaml запись ‘name используется для обозначения переменной типа в обобщенных конструкциях. Rust продолжает эту традицию.
Кроме того, в Rust нашлось еще одно интересное применение подобного синтаксиса — для задания меток:
Параллели с временами жизни тут не случайны: предполагалось, что в будущем в языке появится возможность аннотировать любые блоки подобными метками, которые будут также служить идентификаторами времен жизни их содержимого:
Сложно сказать, будет ли вообще реализована такая функция в языке, но если потребность в ней возникнет, то она органично впишется в существующий синтаксис.
Макросы
Собираем все вместе
Хорошо, допустим, каждый из элементов синтаксиса относительно неплох, но как они сочетаются все вместе? Разве их комбинация не превращает Rust-код в нечитаемое месиво из спецсимволов? Например:
На самом деле это действительно большая проблема. Сигнатуры обобщенных функций с заданными ограничениями, видимо, самые перегруженные синтаксисом элементы языка Rust. И чтобы справиться с ними, Rust предлагает различные синтаксические улучшения и особый сахар, который помогает лучше упорядочить ограничения и скрыть из виду те аспекты, которые и так достаточно очевидны:
Заключение
Как видите, синтаксис Rust не так уж и плох. По крайней мере любой элемент синтаксиса продуман глубже, чем кажется при поверхностном взгляде и выбран именно таким по веским причинам соблюдения баланса простоты, единообразия и выразительности. Отчего же столько негодования и яростных воплей разносится по сети, насчет «токсичности» синтаксиса Rust? Я думаю главной причиной является объективная сложность и непривычность концепций, которые скрываются за этим синтаксисом, а также высокая информативная плотность кода на Rust. Новичку это вселяет ужас, а опытному Rust-программисту облегчает жизнь, так как расширяет его возможности, обеспечивает высокую степень явности и локализованности кода.