Rust — молодой и дерзкий язык программирования
Говорят, что это одновременно C++ и Haskell.
Первая версия языка Rust появилась в 2010 году, и он сразу занял третью строчку в списке любимых языков разработчиков на StackOverflow. Год спустя Rust возглавил этот список и держался там несколько лет. Давайте посмотрим, почему этот язык стал таким популярным, в чём его особенности и почему вокруг него много споров.
В чём идея языка Rust
Автору языка нравилась скорость работы и всемогущество языка C++ и надёжность Haskell. Он поставил перед собой задачу совместить оба этих подхода в одном языке, и за несколько лет он собрал первую версию языка Rust.
Rust позиционируется как компилируемый системный мультипарадигмальный язык высокого уровня. Сейчас поясним, что это значит.
👉 Компилируемый язык означает, что готовая программа — это отдельный файл, который можно запустить на любом компьютере с нужной операционной системой. Для запуска не нужно устанавливать среду разработки и компилятор, достаточно, чтобы скомпилированная версия подходила к вашему компьютеру.
👉 Системный — это когда на языке пишут программы для работы системы в целом. Это могут быть операционные системы, драйверы и служебные утилиты. Обычные программы тоже можно писать на Rust — от калькулятора до системы управления базами данных. Системный язык позволяет писать очень быстрые программы, которые используют все возможности железа.
👉 Мультипарадигмальный значит, что в языке сочетаются несколько парадигм программирования. В случае Rust это ООП, процедурное и функциональное программирование. Причём, ООП в Rust пришло из C++, а функциональное — из Haskell. Программист может сам выбирать, в каком стиле он будет писать код, или совмещать разные подходы в разных элементах программы.
Синтаксис и код
За основу синтаксиса в Rust взят синтаксис из C и C++.Например, классический «Привет, мир!» на Rust выглядит так:
fn main() <
println!(«Hello, world!»);
>
Если вы знакомы с подобным синтаксисом, то сможете быстро начать писать и на Rust. Другое дело, что в Rust есть свои особенности:
let x = if new_game() < 4 >
else if reload() < 3 >
else
Последнее разберём подробно. При такой записи переменная x будет равна четырём, если функция new_game() вернёт значение true. Если этого не случится, компилятор вызовет функцию reload() и проверит, что получилось. Если true, то x примет значение 3, а если и это не сработает — то x станет равным 0.
Ещё в Rust есть сравнение переменной с образцом. В зависимости от того, с каким образцом совпало значение переменной, выполнится та или иная функция:
Главная особенность программ на Rust
Несмотря на синтаксис, похожий на C, главную особенность программ на Rust разработчики взяли из Haskell, и звучит она так:
Если программа на Rust скомпилировалась и не упала во время запуска, то она будет работать до тех пор, пока вы сами её не остановите.
Это значит, что программы на Rust почти так же надёжны, как программы на Haskell. Почти — потому что если программист использует «небезопасный» блок unsafe, который даёт ему прямой доступ к памяти, то в теории это иногда может привести к сбоям. Но даже с такими блоками Rust старается справляться сам и падает только в безнадёжных случаях.
Плюсы и минусы языка
Когда язык совмещает в себе несколько разных подходов из других языков, он получает большинство преимуществ каждого из них:
Минусы в основном связаны со скоростью развития языка. Так как Rust развивается очень быстро, то часто бывает так, что код из старой версии не работает в новой версии. Ещё к минусам можно добавить:
Что написано на Rust
Чаще всего Rust используют в тех проектах, где нужна стабильность и надёжность при высокой нагрузке и общее быстродействие программы.
На практике Rust подходит для разработки ОС, веб-серверов, системных программ мониторинга, веб-движков, а также для создания масштабируемых частей фронтенда и бэкенда. Например, вот самые известные проекты, где Rust был основным языком программирования:
Пять лет Rust
В этом бардаке, который сейчас происходит в мире, легко забыть, что прошло уже пять лет с выпуска 1.0 в 2015 году! Rust за эти пять лет сильно изменился, так что мы хотели бы вспомнить о работе всех участников сообщества, начиная с момента стабилизации языка.
Напомним, если кто забыл: Rust — это язык программирования общего назначения, который обладает средствами, позволяющими строить надёжное и эффективное программное обеспечение. Rust может быть использован в любой области: от ядра вашей операционной системы до вашего следующего web-приложения. Этот язык полностью построен участниками открытого многоликого сообщества, в основном волонтёрами, кто щедро делился своим временем и знаниями для того, чтобы помочь сделать Rust таким, какой он есть сейчас.
Основные изменения с версии 1.0
1.2 — Параллельная кодогенерация: уменьшение времени компиляции всегда являлось главной темой в каждом выпуске Rust, и сейчас трудно представить, что когда-то был короткий период времени, когда Rust вообще не имел параллельной кодогенерации.
1.3 — The Rustonomicon: наш первый выпуск фантастической книги «The Rustonomicon», книги, которая исследует тёмную сторону языка Rust, Unsafe Rust и смежные темы. Она стала прекрасным источником сведений для любого, кто хочет изучить и понять самые трудные аспекты языка.
1.4 — Поддержка Windows MSVC уровня 1: продвижение платформы на уровень поддержки 1 принесло нативную поддержку 64-битной Windows с инструментарием Microsoft Visual C++ (MSVC). До 1.4 вам нужно было устанавливать MinGW (порт окружения GNU под Windows) чтобы использовать и компилировать ваши программы на Rust. Поддержка Windows стала одним из самых больших улучшений Rust за эти пять лет. Лишь недавно Microsoft анонсировала публичный пре-релиз официальной поддержки Rust для WinRT API! И наконец, сейчас стало значительно легче строить высококачественные и кроссплатформенные приложения, нежели это было когда-либо ранее.
1.5 — Cargo Install: добавлена поддержка возможности сборки исполняемых файлов с помощью компилятора Rust вместе с поддержкой предустановленных дополнений cargo породило целую экосистему приложений, утилит и инструментов для разработчиков, которые сообщество обожает и от которых зависит. Cargo теперь поддерживает довольно много команд, которые сначала были просто плагинами, сделанными участниками сообщества и выложенными на crates.io!
1.6 — Libcore: libcore — это подмножество стандартной библиотеки, содержащее только API, которое не требует функций выделения памяти или функций уровня операционной системы. Стабилизация libcore позволила компилировать Rust без выделения памяти или зависимости от операционной системы, что стало одним из первых основных шагов к поддержке Rust для разработки встраиваемых систем.
1.10 — Динамические библиотеки с C ABI: крейт типа cdylib компилируется в динамическую библиотеку с C-интерфейсом, что позволяет встраивать проекты Rust в любую систему, которая поддерживает ABI языка C. Некоторые из самых больших историй успеха Rust среди пользователей — это возможность написать небольшую критическую часть системы на Rust и легко интегрировать в большую кодовую базу. И теперь это стало проще, чем когда-либо.
1.12 — Cargo Workspaces: позволяют организовать несколько проектов Rust и совместно использовать один и тот же lock-файл. Рабочие пространства были неоценимы при создании крупных многоуровневых проектов.
1.14 — Rustup 1.0: Rustup — это менеджер инструментальных средств. Он позволяет беспрепятственно использовать любую версию Rust или любой его инструментарий. То, что начиналось как скромный скрипт, стало тем, что персонал по эксплуатации нежно называет «химера». Возможность обеспечить первоклассное управление версиями компилятора в Linux, macOS, Windows и десятке целевых платформ была мифом ещё пять лет назад.
1.17 — Rustbuild: одним из самых больших улучшений для наших контрибьюторов в разработке языка стал переход от начальной системы сборки на основе make на использование Cargo. Благодаря этому участникам и новичкам rust-lang/rust стало намного проще создавать и вносить свой вклад в проект.
1.20 — Ассоциированные константы: ранее константы могли быть связаны только с модулем. В 1.20 мы стабилизировали ассоциативные константы для структур, перечислений и, что важно, для типажей. Это упростило добавление богатых наборов предустановленных значений в типы вашего API, таких как общие IP-адреса или использующиеся числовые константы.
1.24 — Инкрементальная компиляция: до версии 1.24, когда вы вносили изменения в библиотеку, компилятору приходилось перекомпилировать весь код. Теперь он стал намного умнее в плане кэширования, насколько это было возможно, и ему нужно только заново перегенерировать изменения.
1.26 — impl Trait : добавление impl Trait дало вам выразительные динамические API с преимуществами и производительностью
статической диспетчеризации.
1.31 — 2018 редакция: выпуск 2018 редакции стал нашим самым большим выпуском языка после версии 1.0, добавив изменения синтаксиса и улучшения в код на Rust, написанного полностью обратно совместимым образом. Это позволяет библиотекам, созданным с разными редакциями, беспрепятственно работать вместе.
1.34 — Альтернативный реестр крейтов: поскольку Rust всё больше и больше используется в производстве, возникает большая потребность в возможности размещении и использовании проектов в приватных пространствах. Вместе с этим в Cargo всегда разрешены зависимости из git-репозиториев. С помощью альтернативных реестров ваша организация может легко создать и делиться своим собственным реестром крейтов, которые можно использовать в ваших проектах, как если бы они были на crates.io.
1.39 — Async/Await: стабилизация ключевых слов async / await для обработки Future была одной из главных вех в превращении асинхронного программирования в «объект первого класса» Rust. Уже через шесть месяцев после его выпуска, асинхронное программирование превратилось в разнообразную и производительную экосистему.
Диагностики и ошибки
Одна вещь, которую мы не упомянули, это то насколько улучшены сообщения об ошибках и диагностика в Rust с 1.0. Глядя на старые сообщения об ошибках кажется, что это теперь другой язык.
Мы выделили несколько примеров, которые лучше всего демонстрируют насколько мы улучшили сообщения об ошибках. Теперь они показывают пользователям, где они допустили ошибки, и помогают понять почему код не работает, а также подсказывают, как можно их исправить.
Как я влюбился в Rust и чего мне это стоило
Языков программирования на текущий момент существует превеликое множество. Одни безусловно хороши для определенных целей, другие признаются универсальными и используются многими для решения повседневных задач.
В этой статье я хочу рассказать, как и почему Rust стал для меня основным и любимым языком для решения персональных задач самого разнообразного профиля, и что именно доставляет мне особенное удовольствие при его использовании.
Хочу сразу заметить, что эта статья целиком и полностью — субъективное мнение автора, единственная цель которой — заинтересовать читателей, ценящих в программировании как хобби те же самые вещи, что и он сам, и речь в ней не пойдёт ни о быстродействии, ни о востребованности языка в сфере IT, ни о каких-либо других технических составляющих этой области, вокруг которой часто возникают разного рода споры. Я остановлюсь на том, что Rust — быстрый и безопасный компилируемый ЯП общего назначения. Об остальном — далее.
Какой язык я искал
Лично я в первую очередь делю все ЯП на две большие группы: интерпретируемые и компилируемые. Для личных проектов (разумеется, крупнее скриптов автоматизации) я искал именно второй, так как ключевой для меня была возможность переносить исполняемые файлы на внешних и облачных дисках и запускать их на офисных ПК без каких-либо проблем.
Важным условием при выборе также была возможность без трудностей скомпилировать исполняемые файлы под Windows, Mac OS и дистрибутивы Linux, так как рабочих машин у меня несколько, а запускаться и работать код должен на каждой. Некоторые из проектов шли даже под Raspberry Pi, где мне вдобавок требовалось бережное отношение к памяти. Ну и напоследок я искал простоту в использовании (не в написании кода): чтобы библиотеки ставились (и писались) самым очевидным и удобным образом, чтобы структура проектной директории была простой и понятной, а общение с компилятором – приятным и безболезненным. За ковидный карантин я успел перепробовать множество разных языков, остановившись в итоге на Расте. Давайте узнаем, почему.
Путь к «Hello World»
Так как, пожалуй, большинство читателей ранее с этим языком не взаимодействовали, я начну с самого начала: процесса первого знакомства. В процессе поиска своего идеального ЯП, очень часто я сталкивался с трудностями уже на этом этапе. Где-то были определенные сложности в выборе и настройке IDE, где-то установка или использование компилятора требовало множества разных манипуляций, которые сходу отпугивали и отбивали желание работать. Давайте взглянем, что предстоит пройти человеку, решившему с нуля написать на Расте простейший «Hello World».
Для начала загрузим rustup – программу, которая установит и будет поддерживать в актуальном состоянии все необходимое для написания программ. На Unix-подобных ОС сделать это можно одной командой:
Дополнительные инструкции по установке, а также версия для Windows доступны на официальном сайте.
Вот и все. Для меня впервые путь к «Hello World» оказался невероятно дружелюбным и простым.
Но, разумеется, выводом текста в консоль никто ограничиваться не будет. Следующий шаг – учиться, учиться, и еще раз учиться.
Взглянуть целиком на официальный Quick Start Guide можно здесь
Приключения на пути к познанию
Ключевым моментом для любого, решившего выучить новый ЯП, будет, разумеется, сам процесс изучения. Вопрос доступности и качества документации и справочных материалов здесь встаёт особенно остро. Давайте узнаем, как с этим обстоят дела у Раста.
Спойлер: обстоят они просто замечательно. Одна лишь официальная документация включает в себя множество самых разнообразных изданий, каждое из которых проработано самым детальным образом.
Вот лишь малая часть информации, доступная на официальном сайте:
Вместе с самим языком документация постоянно обновляется и дополняется, а вкупе с множеством форумов и вовсе даёт абсолютно исчерпывающую информацию об использовании. Лично у меня путь от первого знакомства до свободного написания сложных программ и библиотек занял месяц. Много это или мало – судите сами.
Когда знаний и опыта наконец достаточно, самое время написать что-нибудь интересное. Следующее, за что я собираюсь хвалить Раст —
Синтаксис и возможности
Нельзя отрицать, что код на Rust не самый читабельный и очевидный для непосвященного пользователя. Нельзя также не заметить, что вместе с пониманием устройства всех деталей синтаксиса приходит и любовь к нему.
Во первых – точки с запятой и фигурные скобки. Да, многие на дух такое не переносят, считая пережитком прошлого. Я немного другого мнения: при работе с большими объемами кода, который временами приходится кардинально менять, скобки – спасение, а точки с запятой позволяют мне при особо острой необходимости писать последовательности команд одной строкой.
Ставить их везде, кстати, вовсе не обязательно:
Во вторых – функции. Выглядят они в Расте так:
Лично я – ярый сторонник именно такого вида записи, встречающегося и в других языках. Решение, принятое, например, в C++ или C# (с указанием типа возвращаемого значения вместо ключевого слова fn ), на мой взгляд, куда менее очевидно, особенно если приходится иметь дело со сложными типами.
Далее вкратце перечислю мои самые любимые сахара:
Удобоваримый вид импорта модулей
Импорт библиотек реализован здесь максимально кратким и эффективным образом, без лишних ключевых слов и с удобным наследованием:
use std::io::
В крупных проектах с десятками зависимостей в одном файле такие возможности – просто спасение.
Атрибутные макросы
Написание кода, который должен выполниться до сборки программы (к примеру установка порядка условной компиляции), реализовано здесь крайне простым образом.
Match
Match в Расте – продвинутая версия знакомого многим switch/case. Давайте взглянем, на что он способен:
Мощная и удобная штука, которую я использую практически в каждом проекте.
Пара слов об обработке ошибок
Принципы обработки ошибок, конечно, имеют весьма посредственное отношение к синтаксису, но затронуть я хочу их максимально поверхностно, чтобы не нагружать читателя лишней информацией, а лишь продемонстрировать базовые принципы этого процесса.
Error handling – это очень важно. Когда я пишу проект, что должен как можно дольше оставаться в поднятом состоянии и восстанавливаться от любых возможных ошибок, я хочу быть уверенным, что обработал 100% их всех. В этом мне помогает, на мой взгляд, одна из самых важных особенностей языка, ведь я всегда знаю, в каком месте может возникнуть ошибка.
Result работает аналогично, но используется именно для обработки ошибок, возникших во время выполнения кода.
В случае, если в успешном выполнении кода или получении искомого результата мы уверены на все сто, Option и Result могут быть развернуты:
Панику можно вызвать самостоятельно:
Таким образом код получается крайне безопасным, что дает мне лишнюю толику спокойствия.
Последнее слово о cargo
Но как добавить в проект зависимость? Очень просто. В этом нам поможет Cargo.toml – упомянутый ранее файл манифеста, автоматически созданный cargo вместе с нашим проектом.
Ознакомимся с его содержанием:
Процесс поиска и добавления модулей реализован здесь необыкновенно просто:
Эта всемогущая утилита также обладает невероятно детальным выводом сообщений об ошибках и предупреждений, возникших во время сборки,
как при работе из командной строки:
так и с помощью множества официальных плагинов для разных редакторов кода:
Подводим итоги
Вот этим и покорил меня Rust. Невероятным вниманием к деталям, очевидностью процесса сборки и работы с модулями, широкой экосистемой, любопытным синтаксисом и обилием справочных материалов. Он упорядочил работу над моими проектами, поставив ее на поток.
В качестве примера хочу поделиться одним из своих открытых проектов — программой, шифрующей файлы с помощью симметричного алгоритма AES по двум ключам. Я написал ее в попытке создать наиболее простое и легкое кроссплатформенное решение для скрытия своих данных от посторонних глаз. Она поддерживает фильтрацию файлов по размерам, типам, именам, индексам и интервалам и работает с вложенными папками.
Теперь мои планы на будущее – ещё больше погрузиться в изучение этого языка, познав самые тёмные его уголки.
Если у вас был похожий приятный опыт, но касательно другого ЯП, расскажите мне об этом в комментариях, мне будет жутко интересно почитать.
Спасибо за внимание, надеюсь, сегодня вы узнали для себя что-то новое.
P.S. Основой для этой статьи послужили рассказы и опыт одного моего друга.
Облачные серверы от Маклауд отлично подходят за разработки под Rust.
Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!
Погружаемся в логово ржавчины. Как работает компилятор rust
В моей предыдущей статье о rust я попытался рассказать об истории языка, и показать откуда он пришёл. В статье было сделано множество упрощений. Просто нереальное множество. Народу не понравилось. Но в опросе, в конце статьи вы сказали, что надо бы показать кишки компилятора. Ну что же, под катом вы найдёте разбор исходных кодов компилятора rust. Мы проследим путь программы, начиная из исходного файла, прямиком к бинарнику.
Словарь
ICE (Internal compiler error), ошибка компилятора.
Дальнейший текст подразумевает, что вы умеете программировать. Можно и не на rust.
Начало
Поехали. Мы будем лезть нашими ручками в сам компилятор и смотреть на его исходники. Для начала нам понадобятся кое-какие инструменты. Ставим чистую виртуальную машину с Windows 10. Идём в интернеты и льём следующее:
Сорцы компилятора. Достаются с github. Можно лить просто zip, ибо обратно коммитить мы ничего не будем.
Установщик компилятора. Любая свежая стабильная версия подойдёт.
Guide to Rustc Development. Инструкция по разработке компилятора. 460 страниц. Не хило. Сохраняем pdf.
Ну и хорошо. Этого, для начала достаточно. Отключаемся от проводного интернета, хватаем ноутбук и идём на веранду, сидеть и погружаться. Начинаем погружаться, понимаем что будет глупо говорить о компиляторе, если мы не скомпилируем хоть что-то. Ок, так и сделаем.
Ок, это было просто. Но мы не будем использовать cargo для самой компиляции. Используем компилятор напрямую. Но я же на надо cargo издеваюсь, так ведь?
Отступление по теме
Как не надо устанавливать rust
Чего? Так, сам по себе компилятор всё собрал, но ругается на отсутствие линкера. От жеж, зараза. То есть, линкер ему нужен внешний. Ругаемся на компилятор, встаём с удобного кресла и идём обратно, подключаться к проводному интернету, потому что палить 5 гигов установщика Visual Studio Build Tools не хочется на хотспоте.
Билдим всё ещё раз и смотрим.
Ширина и жирина файлов.
Ах, ты, ржавая банка! Какого чёрта?? Я уже как две недели рассказываю всем обитателям Хабра о том, какой ты прекрасный компилятор, и как хорошо ты собираешь минимальные бинарники, а ты. 150 килобайт исполняемого кода из-за одной только линии текста на экране?
Ус недоволен. На него и так уже много чего намотано, он не понимает, почему ему надо разбираться с исходниками раста теперь.
Шаг первый: rustc
Открываем сорцы и наслаждаемся. Всё выглядит очень прилично и чисто. Тут, понятное дело, можно учиться тому как правильно разделять свой проект на куски и как правильно управлять кодом на rust. Собственно говоря, сразу понятно куда идти. Забираемся в compiler/rustc/src/main.rs и смотрим.
Всё только начинается. Держитесь.
Хм. То есть точка входа в программу просто тянет jemalloc вызовы и запускает ещё две функции. Ну вот, всё. Теперь понятно как работает компилятор rust. Делов-то! Кстати, jemalloc это специальный менеджер памяти, изначально разработанный для FreeBSD в 2005 году. Основной упор был сделан на то, чтобы избежать фрагментации памяти при работе с этим аллокатором. В оригинальной версии он просто заменяет malloc. В 2007 году Firefox начал использовать этот менеджер для снижения расхода памяти, а ещё через пару лет он попал в Facebook.
Шаг второй: rustc-driver
Ладно, всё выглядит слишком уж просто. Погружаемся дальше. rustc тянет за собой rustc-driver. Ныряем туда.
Тут мы найдём небольшой readme, который расскажет нам о том, что компилятора в самом драйвере мы не найдём. Эта программа собирает конфигурацию из аргументов и запускает сам процесс компиляции из других крейтов. После изучения исходников находим функцию для запуска процесса компиляции.
Да, в этом крейте файлов не так-то много, но что бы тут не творилось, на самом деле всё сводится к вызову методов в крейте под названием interface. Вышеприведённый код это и показывает. interface::run_compiler и поехали.
Что же произошло в rustc-driver? Мы собрали все конфиги. Подгрузили все файлы и нашли их местоположение в файловой системе. Создали замыкание, которое следит за процессом компиляции и запускает линкер после успешной компиляции. Запустили линтеры (если такие имелись) и приготовили сам компилятор к запуску. Давайте запускать.
Шак третий: rustc-interface
Ага. Тут мы уже ближе к самому процессу компиляции. Все конфиги подъедены, файлы тоже замеплены. Смотрим на исходники интерфейса. Их хоть и не так-то много, но это наш центральный вокзал, где куча других крейтов собирается воедино.
Так, осматриваемся и находим
Кстати тут же, недалеко, мы можем найти настройку механизма кодогенерации.
Быстренько посмотрим на наши сорцы и увидим что у нас прямо в сорцах есть 3 различных модуля кодогенерации. Что они делают? Превращают MIR в конечный код для системы компиляции. Открываем rustc-codegen-llvm и смотрим в README:
Ок, ну тут всё понятно, мы берём MIR и переделываем его в LLVM IR. После этого LLVM может скомпилировать код в конечный бинарник. Но погодите, помимо LLVM бекенда у нас есть ещё два других! Смотрим туда. rustc-codegen-ssa согласно документации, позволяет генерировать низкоуровневый код, который не будет привязан к определённому бекэнду (например, LLVM) и позволит в дальнейшем использовать другие системы компиляции.
Собственно говоря, прямо там же вы найдёте rustc-codegen-cranelift. То есть MIR в будущем может компилироваться через cranelift, который в идеале ускорит процесс компиляции. Ну это в будущем, пока что проект в процессе тестирования и работает не лучше, чем Газель без мотора.
Открываем модуль и смотрим, что происходит внутри:
Ага, вот тут мы берём быка за рога и начинаем разбирать исходный код на части. Далее, создаём и проверяем AST
Даже если вы и поменяли что-то в каком-либо файле, то благодаря системе запросов вы сможете избежать ненужной перекомпиляции. Что если вы изменили только одну линию в комментариях к файлу? Пересобирать такой не придётся.
Давайте посмотрим на запросы, которые создаёт компилятор:
В итоге у нас на выходе получается большая и толстая структура:
И как раз её можно дёргать для выполнения необходимых запросов.
Шаг четвёртый: rustc-parse и rustc-lexer
Далее по тексту вы найдёте простую логику всех этих запросов. «Простая» логика заключается в вызове крейтов, которые её обрабатывают. Например, rustc-parse. Это крейт, который использует rustc-lexer. Лексер читает строки из файлов и преобразовывает их в очень простые токены. Токены передаются парсеру, который превращает их в Span и продолжает работу с кодом. Основной момент этого Span заключается в том, что к каждому элементу в дереве кода будет добавлена информация о том, в каком конкретно месте этот элемент записан в исходном файле. Когда компилятор будет сообщать об ошибке, вы увидите, где именно эта ошибка произошла.
Шаг пятый: rustc-expand
В результате работы парсера мы получаем наш самый великий и могучий AST.
Всё это создаётся огромным макросом astfragments! в \compiler\rustcexpand\src\expand.rs
AST используется для дальнейшей генерации кода и приведения его в нужный вид. Про это можно писать отдельную книгу. Но мы пока удовольствуемся там, что AST можно разобрать до HIR.
Шаг шестой: rustc-middle
Этим как раз и занимается rustc-middle. Вернее, не только этим. Залезаем в исходники и видим что тут у нас есть HIR, MIR и Types.
Здесь весь синтаксический сахар растворяется в чае и перестаёт быть сахаром. Так моя любимая for node in data превращается в
С HIR теперь можно работать…
Шаг седьмой: rustc_ty
Последняя тянется через весь процесс компиляции.
Файл просто огромный. Нам надо вычислить типы каждой переменной, замыкания и трейта. Сам модуль занимает более 3000 строк, не считая остальные файлы в директории.
Кстати, смотрим в rust-master\compiler\rustc_typeck\src\check\expr.rs
Компилируем и запускаем:
Пасхалки они выглядят именно вот так.
Так, вычислили типы и теперь можем проверить что никто не пытается запихнуть строку в Int. Хорошо. Можно идти дальше.
Шаг восьмой: rustc_mir и rustc_mir_build
Теперь наш HIR можно преобразовать в MIR. Берём ранее созданный TyCtxt и начинаем преобразовывать его в
И так далее по всем нодам. MIR это намного более генерализированная версия HIR. Она очень близка к тому что требует от нас LLVM для компиляции. В результате этой генерализации мы можем намного более эффективно работать над оптимизацией написанного вами кода и заниматься проверками заимствований и оптимизацией.
Шаг девятый: Проверка заимствования
Самая «страшная» функция rust это всем известный borrow cheker. Сам он живёт в
Шаг десятый: Оптимизации
Шак одиннадцатый: прощай, rust!
Полученный оптимизированный MIR можно теперь переделать в LLVM IR. Поехали. rustc-codegen-llvm создаёт LLVM-IR на базе MIR, который мы сгенерировали на предыдущем этапе. Здесь заканчивается rust и начинается llvm. Хотя, мы ещё не закончили с сорцами компилятора.
Копаемся чуть глубже и находим rustc-target в котором видим различные дополнительные классы для работы с определённым ассемблером.
После того как кодогенерация завершена, мы можем передать IR в сам LLVM. rustc_llvm нам в помощь.
Вот, собственно говоря, и всё, ребята! LLVM за пределами нашей видимости. На моей операционной системе Visual Studio Build Tools берут на себя контроль и перегоняют LLVMIR в обычный бинарник.
Он парсится из текста в AST.
AST обрабатывается и оптимизируется в HIR
HIR обрабатывается и оптимизируется в MIR.
MIR делает проверки заимствования и оптимизацию и перегоняется в LLVMIR.
LLVMIR компилируется на конечной платформе.
Пробуем ручками
Ну что же, напоследок осталось написать простенькую программку, типа этого:
И начать её компилировать, только показывая все внутренности. Для начала есть замечательная опция компилятора, которая работает на любой версии:
Значит, запуская компиляцию следующим образом:
Мы получаем на выходе мириады различных форматов, включая сгенерированный ассемблеровский код, байткод и IR для LLVM, и даже челвоеко-читаемый MIR.
А если у вас есть nightly компилятор, то вы можете запустить
И полюбоваться вашим HIR, в то время как
Даст вам возможность посмотреть на то, как выглядит AST.
Напоследок
Ну что же, мы залезли в дебри компилятора. Теперь вы знаете, что происходит каждый раз когда вы запускаете билд на своей машине. Я показал вам rust. Но не бойтесь, ваш любимый язык, скорее всего, ничуть не менее сложен. Проще будет только компилировать ассемблер для 386 под досом. И не важно, если вы запускаете C#, Java, Javascript, Golang или haskell. Происходить будет многое, хотя и совсем по-разному.
Понятно? Ну и хорошо.





