use в раст что это

Так ли токсичен синтаксис 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-программисту облегчает жизнь, так как расширяет его возможности, обеспечивает высокую степень явности и локализованности кода.

Источник

The Rust Programming Language

Подключение путей в область видимости с помощью ключевого слова use

Листинг 7-11. Подключение модуля в область видимости с помощью use

Добавление use и пути в область видимости аналогично созданию символической ссылки в файловой системе. Добавляя use crate::front_of_house::hosting в корень крейта, hosting теперь является допустимым именем в этой области, как если бы hosting модуль был определён в корне крейта. Пути подключённые в область видимости с помощью use также проверяют конфиденциальность как и любые другие пути.

Также можно подключить элемент в область видимости с помощью use и относительного пути. Листинг 7-12 показывает как указать относительный путь, чтобы получить то же поведение, что и в листинге 7-11.

Листинг 7-12. Подключение модуля в область видимости с помощью use и относительного пути

Создание идиоматических путей с use

Листинг 7-13. Подключение функции add_to_waitlist в область видимости с помощью use не идиоматическим способом

Листинг 7-14. Подключение HashMap в область видимости идиоматическим способом

За этой идиомой нет веской причины: это просто соглашение, которое появилось само собой. Люди привыкли читать и писать код Rust таким образом.

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

Предоставление новых имён с помощью ключевого слова as

Листинг 7-16. Переименование типа с помощью ключевого слова as при его подключении в область видимости

Реэкспорт имён с pub use

Листинг 7-17. Делаем при помощи pub use имя доступным для любого кода из новой области видимости

Использование внешних пакетов

Добавление rand в качестве зависимости в Cargo.toml указывает Cargo загрузить пакет rand и любые требующиеся для работы этого пакета зависимости из crates.io и сделать rand доступным для нашего проекта.

Затем, чтобы подключить определения rand в область видимости нашего пакета, мы добавили строку use начинающуюся с названия пакета rand и списка элементов, которые мы хотим подключить в область видимости. Напомним, что в разделе «Генерация случайного числа» Главы 2, мы подключили типаж Rng в область видимости и вызвали функцию rand::thread_rng :

Члены сообщества Rust сделали много пакетов доступными на ресурсе crates.io, и добавление любого из них в свой пакет включает в себя одни и те же шаги: добавить пакет в файл Cargo.toml вашего пакета, использовать use для подключения элементов этого пакета в область видимости.

Использование вложенных путей для уменьшения длинных списков use

Если мы используем несколько элементов определённых в одном пакете или в том же модуле, то перечисление каждого элемента в отдельной строке может занимать много вертикального пространства в файле. Например, эти два объявления use используются в программе угадывания числа (листинг 2-4) для подключения элементов из std в область видимости:

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

Листинг 7-18. Указание вложенных путей для подключения в область видимости нескольких элементов с одинаковым префиксом

Листинг 7-19. Два оператора use где один содержит часть пути другого

Листинг 7-20. Объединение путей из листинга 7-19 в один оператор use

Эта строка подключает std::io и std::io::Write в область видимости.

Оператор * (Glob)

Если хотим подключить в область видимости все общие элементы, определённые в пути, можно указать путь за которым следует оператор * (звёздочка, glob):

Оператор * часто используется при тестировании для подключения всего что есть в модуле tests ; мы поговорим об этом в разделе «Как писать тесты» Главы 11. Оператор * также иногда используется как часть шаблона автоматического импорта (prelude): смотрите документацию по стандартной библиотеке для получения дополнительной информации об этом шаблоне.

Источник

Обзор языка программирования Rust

Rust — новый экспериментальный язык программирования, разрабатываемый Mozilla. Язык компилируемый и мультипарадигмальный, позиционируется как альтернатива С/С++, что уже само по себе интересно, так как даже претендентов на конкуренцию не так уж и много. Можно вспомнить D Вальтера Брайта или Go от Google.
В Rust поддерживаются функицональное, параллельное, процедурное и объектно-ориентированное программирование, т.е. почти весь спектр реально используемых в прикладном программировании парадигм.

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

Первое впечатление

Синтаксис языка строится в традиционном си-подобном стиле (что не может не радовать, так как это уже стандарт де-факто). Естественно, всем известные ошибки дизайна С/С++ учтены.
Традиционный Hello World выглядит так:

Пример чуть посложнее — функция расчета факториала:

Как видно из примера, функции объявляются в «функциональном» стиле (такой стиль имеет некоторые преимущества перед традиционным «int fac(int n)»). Видим автоматический вывод типов (ключевое слово let), отсутствие круглых скобок у аргумента while (аналогично Go). Еще сразу бросается в глаза компактность ключевых слов. Создатели Rust дейтсвительно целенаправленно сделали все ключевые слова как можно более короткими, и, скажу честно, мне это нравится.

Мелкие, но интересные синтаксические особенности

Типы данных

Rust, подобно Go, поддерживает структурную типизацию (хотя, по утверждению авторов, языки развивались независимо, так что это влияние их общих предшественников — Alef, Limbo и т.д.). Что такое структурная типизация? Например, у вас в каком-то файле объявлена структура (или, в терминологии Rust, «запись»)
type point = ;
Вы можете объявить кучу переменных и функций с типами аргументов «point». Затем, где-нибудь в другом месте, вы можете объявить какую-нибудь другую структуру, например
type MySuperPoint = ;
и переменные этого типа будут полностью совместимы с переменными типа point.

В противоположность этому, номинативная типизация, принятая в С, С++,C# и Java таких конструкций не допускает. При номинативной типизации каждая структура — это уникальный тип, по умолчанию несовместимый с другими типами.

Структуры в Rust называются «записи» (record). Также имеются кортежи — это те же записи, но с безымянными полями. Элементы кортежа, в отличие от элементов записи, не могут быть изменяемыми.

Имеются вектора — в чем-то подобные обычным массивам, а в чем-то — типу std::vector из stl. При инициализации списком используются квадратные скобки, а не фигурные как в С/С++

Вектор, тем ни менее — динамическая структура данных, в частности, вектора поддерживают конкатенацию.

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

Язык поддерживает так называемые теги. Это не что иное, как union из Си, с дополнительным полем — кодом используемого варианта (то есть нечто общее между объединением и перечислением). Или, с точки зрения теории — алгебраический тип данных.

В простейшем случае тег идентичен перечислению:

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

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

Сопоставление с образцом (pattern matching)

Для начала можно рассматривать паттерн матчинг как улучшенный switch. Используется ключевое слово alt, после которого следует анализируемое выражение, а затем в теле оператора — паттерны и действия в случае совпадения с паттернами.

В качестве «паттеронов» можно использовать не только константы (как в Си), но и более сложные выражения — переменные, кортежи, диапазоны, типы, символы-заполнители (placeholders, ‘_’). Можно прописывать дополнительные условия с помощью оператора when, следующего сразу за паттерном. Существует специальный вариант оператора для матчинга типов. Такое возможно, поскольку в языке присутствует универсальный вариантный тип any, объекты которого могут содержать значения любого типа.

Указатели. Кроме обычных «сишных» указателей, в Rust поддерживаются специальные «умные» указатели со встроенным подсчетом ссылок — разделяемые (Shared boxes) и уникальные (Unique boxes). Они в чем-то подобны shared_ptr и unique_ptr из С++. Они имеют свой синтаксис: @ для разделяемых и

для уникальных. Для уникальных указателей вместо копирования существует специальная операция — перемещение:

после такого перемещения указатель x деинициализируется.

Замыкания, частичное применение, итераторы

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

1. Ключевое слово lambda используется для объявления вложенной функции или функционального типа данных.

В этом примере мы имеем функцию make_plus_function, принимающую один аргумент «x» типа int и возвращающую функцию типа «int->int» (здесь lambda — ключевое слово). В теле функции описывается эта самая фунция. Немного сбивает с толку отсутствие оператора «return», впрочем, для ФП это обычное дело.

2. Ключевое слово block используется для объявления функционального типа — аргумента функции, в качестве которого можно подставить нечто, похожее на блок обычного кода.

Здесь мы имеем функцию, на вход которой подается блок — по сути лямбда-функция типа «int->int», и вектор типа int (о синтаксисе векторов далее). Сам «блок» в вызывающем коде записыавется с помощью несколько необычного синтаксиса <|x| x + 1 >. Лично мне больше нравятся лямбды в C#, символ | упорно воспринимается как битовое ИЛИ (которое, кстати, в Rust также есть, как и все старые добные сишные операции).

3. Частичное применение — это создание функции на основе другой функции с большим количеством аргументов путем указания значений некоторых аргументов этой другой функции. Для этого используется ключевое слово bind и символ-заполнитель «_»:

Чтобы было понятнее, скажу сразу, что такое можно сделать на обычном Си путем создания простейшей обертки, как-то так:
const char* daynum (int i) < const char *s =<"mo", "tu", "we", "do", "fr", "sa", "su">; return s[i]; >

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

Еще пример: объявляется функция add с двумя аргументами int, возвращающая int. Далее объявляется функциональный тип single_param_fn, имеющий один аргумент int и возвращающий int. С помощью bind объявляются два функциональных объекта add4 и add5, построенные на основе функции add, у которой частично заданы аргументы.

Функциональные объекты можно вызывать также, как и обычные функции.

4. Чистые функции и предикаты
Чистые (pure) функции — это функции, не имеющие побочных эффектов (в том числе не вызывающие никаких других функций, кроме чистых). Такие функции выдяляются ключевым словом pure.

Предикаты — это чистые (pure) функции, возвращающие тип bool. Такие функции могут использоваться в системе typestate (см. дальше), то есть вызываться на этапе компиляции для различных статических проверок.

Синтаксические макросы
Планируемая фича, но очень полезная. В Rust она пока на стадии начальной разработки.

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

Атрибуты

Концепция, похожая на атрибуты C# (и даже со схожим синтаксисом). За это разработчикам отдельное спасибо. Как и следовало ожидать, атрибуты добавляют метаинформацию к той сущности, которую они аннотируют,

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

Параллельные вычисления

Пожалуй, одна из наиблее интересных частей языка. При этом в tutorial на данный момент не описана вообще:)
Программа на Rust состоит из «дерева задач». Каждая задача имеет функцию входа, собственный стек, средства взаимодействия с другими задачами — каналы для исходящей информации и порты для входящей, и владеет некоторой частью объектов в динамической куче.
Множество задач Rust могут существовать в рамках одного процесса операционной системы. Задачи Rust «легковесные»: каждая задача потребляет меньше памяти чем процесс ОС, и переключение между ними осуществляется быстрее чем переключение между процессами ОС (тут, вероятно, имеются в виду все-же «потоки»).

Задача состоит как минимум из одной функции без аргументов. Запуск задачи осуществляется с помощью функции spawn. Каждая задача может иметь каналы, с помощью которых она передает инфорацию другим задачам. Канал — это специальный шаблонный тип chan, параметризируемый типом данных канала. Например, chan — канал для передачи беззнаковых байтов.
Для передачи в канал используется функция send, первым аргументом которой является канал, а вторым — значение для передачи. Фактически эта функция помещает значение во внутренний буфер канала.
Для приема данных используются порты. Порт — это шаблонный тип port, параметризируемый типом данных порта: port — порт для приема беззнаковых байтов.
Для чтения из портов используется функция recv, аргументом которой является порт, а возвращаемым значением — данные из порта. Чтение блокирует задачу, т.е. если порт пуст, задача переходит в состояние ожидания до тех пор, пока другая задача не отправит на связанный с портом канал данные.
Связывание каналов с портами происходит очень просто — путем инициализации канала портом с помощью ключевого слова chan:
let reqport = port();
let reqchan = chan(reqport);
Несколько каналов могут быть подключены к одному порту, но не наоборот — один канал не может быть подключен одновременно к нескольким портам.

Typestate

Общепринятого перевода на русский понятия «typestate» я так и не нашел, поэтому буду называть это «состояния типов». Суть этой фичи в том, что кроме обычного контроля типов, принятого в статической типизации, возможны дополнительные контекстные проверки на этапе компиляции.
В том или ином виде состояния типов знакомы всем программистам — по сообщениям компилятора «переменная используется без инициализации». Компилятор определяет места, где переменная, в которую ни разу не было записи, используется для чтения, и выдает предупреждение. В более общем виде эта идея выглядит так: у каждого объекта есть набор состояний, которые он может принимать. В каждом состоянии для этого объекта определены допустимые и недопустимые операции. И компилятор может выполнять проверки — допустима ли конкретная операция над объектом в том или ином месте программы. Важно, что эти проверки выполняются на этапе компиляции.

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

В Rust существует понятие «предикаты» — специальные функции, не имеющие побочных эффектов и возвращающие тип bool. Такие функции могут использоваться компилятором для вызова на этапе компиляции с целью статических проверок тех или иных условий.

Ограничения (constraints) — это специальные проверки, которые могут выполняться на этапе компиляции. Для этого используется ключевое слово check.

Предикаты могут «навешиваться» на входные параметры функций таким вот способом:

Информации по typestate крайне мало, так что многие моменты пока непонятны, но концепция в любом случае интересная.

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *