Системные языки
Системными языками программирования обычно называют такие, на которых создают низкоуровневые ПО вроде операционных систем, драйверов или новые языки программирования. В них есть возможности для прямого доступа к ресурсам компьютера: памяти или регистрам процессора. Такие языки очень сильно заточены на производительность. Это не значит, что на них нельзя создавать web или нативные приложения. Конечно можно. Но чаще их используют там, где нужна максимальная скорость.
Одним из таких языков является язык Rust. В сравнении со своими соседями C/C++ он является достаточно молодым и первая стабильная версия появилась в 2015 году. Создатели языка уделили огромное внимание трем вещам: параллелизму, скорости и безопасности.
Rust со своими интересными концепциями владения и заимствования, а также встроенным в компилятор механизмом проверки ссылок уже на этапе компиляции гарантирует, что приложение обеспечит безопасную работу с памятью. Один из участников Reddit сказал: «Программирование на Rust — это как паркур со страховкой и в защите; иногда это выглядит странно, но вы можете делать многие трюки, не боясь сломать себе что-нибудь».
Системными языками программирования обычно называют такие, на которых создают низкоуровневые ПО вроде операционных систем, драйверов или новые языки программирования. В них есть возможности для прямого доступа к ресурсам компьютера: памяти или регистрам процессора. Такие языки очень сильно заточены на производительность. Это не значит, что на них нельзя создавать web или нативные приложения. Конечно можно. Но чаще их используют там, где нужна максимальная скорость.
Одним из таких языков является язык Rust. В сравнении со своими соседями C/C++ он является достаточно молодым и первая стабильная версия появилась в 2015 году. Создатели языка уделили огромное внимание трем вещам: параллелизму, скорости и безопасности.
Rust со своими интересными концепциями владения и заимствования, а также встроенным в компилятор механизмом проверки ссылок уже на этапе компиляции гарантирует, что приложение обеспечит безопасную работу с памятью. Один из участников Reddit сказал: «Программирование на Rust — это как паркур со страховкой и в защите; иногда это выглядит странно, но вы можете делать многие трюки, не боясь сломать себе что-нибудь».
Стек и куча
В большинстве языков разработчик не думает о том, как данные хранятся в памяти.
Многие языки программирования не требуют, чтобы вы слишком часто думали о стеке и куче. Но в языках системного программирования, одним из которых является Rust, то, какое значение находится в стеке или в куче, влияет на поведение языка и на принятие вами определённых решений.
И стек, и куча — это части памяти, доступные вашему коду для использования во время выполнения. Однако они структурированы по-разному. Стек хранит значения в порядке их получения, а удаляет — в обратном. Это называется *«последним пришёл — первым ушёл»*. Подумайте о стопке тарелок: когда вы добавляете тарелки, вы кладёте их сверху стопки — когда вам нужна тарелка, вы берёте одну так же сверху. Добавление или удаление тарелок посередине или снизу не сработает! Добавление данных называется *помещением в стек*, а удаление — извлечением *из стека*. Все данные, хранящиеся в стеке, должны иметь известный фиксированный размер. Данные, размер которых во время компиляции неизвестен или может измениться, должны храниться в куче.
Куча устроена менее организованно: когда вы кладёте данные в кучу, вы запрашиваете определённый объём пространства. Операционная система находит в куче свободный участок памяти достаточного размера, помечает его как используемый и возвращает *указатель*, являющийся адресом этого участка памяти. Этот процесс называется *выделением памяти в куче* и иногда сокращается до *выделения памяти* (помещение значений в стек не считается выделением). Поскольку указатель на участок памяти в куче имеет определённый фиксированный размер, его можно расположить в стеке, однако когда вам понадобятся актуальные данные, вам придётся проследовать по указателю. Представьте, что вы сидите в ресторане. Когда вы входите, вы называете количество человек в вашей группе, и персонал находит свободный стол, которого хватит на всех, и ведёт вас туда. Если кто-то из вашей группы опоздает, он может спросить, куда вас посадили, чтобы найти вас.
Помещение в стек происходит более быстро, чем выделение памяти в куче, потому что операционная система не должна искать место для размещения информации — это место всегда на верхушке стека. Для сравнения, выделение памяти в куче требует больше работы, потому что операционная система сначала должна найти участок памяти достаточного размера, а затем произвести некоторые действия для подготовки к следующему выделению памяти.
Доступ к данным в куче медленнее, чем доступ к данным в стеке, потому что вам нужно следовать по адресу указателя, чтобы добраться туда. Современные процессоры работают быстрее, если они меньше прыгают по памяти. Продолжая аналогию, рассмотрим официанта в ресторане, принимающего заказы со многих столов. Наиболее эффективно будет получить все заказы за одним столом, прежде чем переходить к следующему столу. Получение заказа со стола А, затем со стола В, затем снова одного с А, а затем снова одного с В было бы гораздо более медленным делом. Точно так же процессор может выполнять свою работу лучше, если он работает с данными, которые находятся близко к другим данным (как в стеке), а не далеко (как это может быть в куче).
В большинстве языков разработчик не думает о том, как данные хранятся в памяти.
Многие языки программирования не требуют, чтобы вы слишком часто думали о стеке и куче. Но в языках системного программирования, одним из которых является Rust, то, какое значение находится в стеке или в куче, влияет на поведение языка и на принятие вами определённых решений.
И стек, и куча — это части памяти, доступные вашему коду для использования во время выполнения. Однако они структурированы по-разному. Стек хранит значения в порядке их получения, а удаляет — в обратном. Это называется *«последним пришёл — первым ушёл»*. Подумайте о стопке тарелок: когда вы добавляете тарелки, вы кладёте их сверху стопки — когда вам нужна тарелка, вы берёте одну так же сверху. Добавление или удаление тарелок посередине или снизу не сработает! Добавление данных называется *помещением в стек*, а удаление — извлечением *из стека*. Все данные, хранящиеся в стеке, должны иметь известный фиксированный размер. Данные, размер которых во время компиляции неизвестен или может измениться, должны храниться в куче.
Куча устроена менее организованно: когда вы кладёте данные в кучу, вы запрашиваете определённый объём пространства. Операционная система находит в куче свободный участок памяти достаточного размера, помечает его как используемый и возвращает *указатель*, являющийся адресом этого участка памяти. Этот процесс называется *выделением памяти в куче* и иногда сокращается до *выделения памяти* (помещение значений в стек не считается выделением). Поскольку указатель на участок памяти в куче имеет определённый фиксированный размер, его можно расположить в стеке, однако когда вам понадобятся актуальные данные, вам придётся проследовать по указателю. Представьте, что вы сидите в ресторане. Когда вы входите, вы называете количество человек в вашей группе, и персонал находит свободный стол, которого хватит на всех, и ведёт вас туда. Если кто-то из вашей группы опоздает, он может спросить, куда вас посадили, чтобы найти вас.
Помещение в стек происходит более быстро, чем выделение памяти в куче, потому что операционная система не должна искать место для размещения информации — это место всегда на верхушке стека. Для сравнения, выделение памяти в куче требует больше работы, потому что операционная система сначала должна найти участок памяти достаточного размера, а затем произвести некоторые действия для подготовки к следующему выделению памяти.
Доступ к данным в куче медленнее, чем доступ к данным в стеке, потому что вам нужно следовать по адресу указателя, чтобы добраться туда. Современные процессоры работают быстрее, если они меньше прыгают по памяти. Продолжая аналогию, рассмотрим официанта в ресторане, принимающего заказы со многих столов. Наиболее эффективно будет получить все заказы за одним столом, прежде чем переходить к следующему столу. Получение заказа со стола А, затем со стола В, затем снова одного с А, а затем снова одного с В было бы гораздо более медленным делом. Точно так же процессор может выполнять свою работу лучше, если он работает с данными, которые находятся близко к другим данным (как в стеке), а не далеко (как это может быть в куче).
TOML
TOML (Tom's Obvious, Minimal Language) — это простой язык конфигурации, разработанный Томом Престон-Вернером (создатель SemVer, основатель Gravatar и соучредитель GitHub). Язык TOML был создан для обеспечения удобочитаемости и легкости в использовании при написании файлов конфигурации. TOML имеет простой и понятный синтаксис, использующий пары ключ/значение. Файлы конфигурации описываются в файлах с расширением *.toml.
Активно используется в Rust для указания зависимостей библиотек Cargo (менеджер пакетов и система сборки в Rust). В Rust можно начинать знакомство с библиотекой с файла cargo.toml. В нем описаны зависимости и информация о библиотеке.
Основные особенности языка TOML включают:
• Поддержка различных типов данных, таких как строки, числа, булевы значения и даты.
• Возможность использования комментариев для пояснения значений в файле конфигурации.
• Поддержка вложенных структур данных и списков.
• Поддержка многострочных строк и специальных символов.
• Легкость в чтении и записи файлов конфигурации.
Язык TOML является очень гибким и удобным для использования при настройке и конфигурировании различных приложений и систем.
Вот несколько примеров синтаксиса TOML:
Это лишь некоторые примеры синтаксиса языка TOML. Больше примеров в документации.
TOML (Tom's Obvious, Minimal Language) — это простой язык конфигурации, разработанный Томом Престон-Вернером (создатель SemVer, основатель Gravatar и соучредитель GitHub). Язык TOML был создан для обеспечения удобочитаемости и легкости в использовании при написании файлов конфигурации. TOML имеет простой и понятный синтаксис, использующий пары ключ/значение. Файлы конфигурации описываются в файлах с расширением *.toml.
Активно используется в Rust для указания зависимостей библиотек Cargo (менеджер пакетов и система сборки в Rust). В Rust можно начинать знакомство с библиотекой с файла cargo.toml. В нем описаны зависимости и информация о библиотеке.
Основные особенности языка TOML включают:
• Поддержка различных типов данных, таких как строки, числа, булевы значения и даты.
• Возможность использования комментариев для пояснения значений в файле конфигурации.
• Поддержка вложенных структур данных и списков.
• Поддержка многострочных строк и специальных символов.
• Легкость в чтении и записи файлов конфигурации.
Язык TOML является очень гибким и удобным для использования при настройке и конфигурировании различных приложений и систем.
Вот несколько примеров синтаксиса TOML:
# Простые параметры ключ = значение. Поддерживаются разные типы
integer = 12
float = 1.23
string = "not empty string"
datetime = 2023-11-19
# Пример многострочной строки
description = """
Это многострочный текст.
Можно использовать несколько строк.
"""
# Объявление таблицы начинается с [table_name].
# Пустая строка в конце отделяет таблицу от других параметров.
# Запись ниже эквивалент в json {"server": {"ip": "127.0.0.1", "port": 8080}}
[server]
ip = "127.0.0.1"
port = 8080
# Пример списка
fruits = ["apple", "banana", "orange"]
# Несколько вариантов описания вложенной структуры
[database]
host = "localhost"
port = 5432
username = "admin"
password = "password"
# Или такой вариант
database_second = { host = "localhost", port = 5432, username = "admin", password = "password"}
# третий вариант
database_third.host = "localhost"
database_third.port = 5432
database_third.username = "admin"
database_third.password = "password"
Это лишь некоторые примеры синтаксиса языка TOML. Больше примеров в документации.
❤1👍1
rust.pdf
101.3 KB
Есть знаменитый сайт в среде IT, на котором публикуются дорожные карты для развития в той или иной технологии. Называется он roadmap.sh
Недавно вышла дорожная карта по языку Rust. Отлично подойдет для развития в языке.
Недавно вышла дорожная карта по языку Rust. Отлично подойдет для развития в языке.
Зачем изучать СИКП
СИКП (Структура и интерпретация компьютерных программ) является одним из наиболее известных учебников по программированию. Изучение СИКП полезно по нескольким причинам:
1. Фундаментальное понимание: СИКП дает глубокое понимание того, как работают компьютерные программы. Это помогает стать лучшим программистом, который может создавать более эффективные и надежные программы.
2. Универсальные концепции: СИКП обучает универсальным концепциям программирования, которые применимы в любом языке программирования. Это делает вас более гибким программистом, способным работать с различными технологиями.
3. Разработка аналитического мышления: Изучение СИКП требует и развивает аналитическое мышление. Это умение полезно не только в программировании, но и во многих других областях.
4. Улучшение навыков решения проблем: СИКП предлагает множество сложных проблем для решения, что помогает развить навыки решения проблем и уверенность в своих способностях.
Можно использовать чек-лист для изучения на хекслете: https://sicp.hexlet.io/ru
Также от них есть бесплатный курс по СИКП на youtube.
СИКП (Структура и интерпретация компьютерных программ) является одним из наиболее известных учебников по программированию. Изучение СИКП полезно по нескольким причинам:
1. Фундаментальное понимание: СИКП дает глубокое понимание того, как работают компьютерные программы. Это помогает стать лучшим программистом, который может создавать более эффективные и надежные программы.
2. Универсальные концепции: СИКП обучает универсальным концепциям программирования, которые применимы в любом языке программирования. Это делает вас более гибким программистом, способным работать с различными технологиями.
3. Разработка аналитического мышления: Изучение СИКП требует и развивает аналитическое мышление. Это умение полезно не только в программировании, но и во многих других областях.
4. Улучшение навыков решения проблем: СИКП предлагает множество сложных проблем для решения, что помогает развить навыки решения проблем и уверенность в своих способностях.
Можно использовать чек-лист для изучения на хекслете: https://sicp.hexlet.io/ru
Также от них есть бесплатный курс по СИКП на youtube.
🤡1
Garbage Collector
Сборщик мусора (Garbage Collector, GC) — это автоматическое управление памятью, которое избавляет разработчиков от ручного выделения и освобождения памяти, предотвращая ошибки и сбои программы.
Сборщик мусора (GC) отслеживает все объекты в памяти программы, определяет их доступность и автоматически освобождает память от "мусора" — недоступных объектов.
Сборщики мусора используются во многих языках программирования, включая Java, C#, Python и JavaScript. Однако у GC есть некоторые недостатки, например его влияние на производительность из-за необходимости регулярного сканирования памяти и возможности непредсказуемых задержек во время сбора мусора.
Все программы так или иначе должны управлять тем, как они используют память компьютера во время работы. Некоторые языки имеют сборщик мусора, регулярно отслеживающий неиспользуемую память во время работы программы; в других языках программист должен явно выделять и освобождать память. В Rust используется третий подход: память управляется через систему владения с набором правил, которые проверяются компилятором. При нарушении любого из правил программа не будет скомпилирована.
Сборщик мусора (Garbage Collector, GC) — это автоматическое управление памятью, которое избавляет разработчиков от ручного выделения и освобождения памяти, предотвращая ошибки и сбои программы.
Сборщик мусора (GC) отслеживает все объекты в памяти программы, определяет их доступность и автоматически освобождает память от "мусора" — недоступных объектов.
Сборщики мусора используются во многих языках программирования, включая Java, C#, Python и JavaScript. Однако у GC есть некоторые недостатки, например его влияние на производительность из-за необходимости регулярного сканирования памяти и возможности непредсказуемых задержек во время сбора мусора.
Все программы так или иначе должны управлять тем, как они используют память компьютера во время работы. Некоторые языки имеют сборщик мусора, регулярно отслеживающий неиспользуемую память во время работы программы; в других языках программист должен явно выделять и освобождать память. В Rust используется третий подход: память управляется через систему владения с набором правил, которые проверяются компилятором. При нарушении любого из правил программа не будет скомпилирована.
Zero Cost Abstractions
Благодаря отсутствию GC в Rust мы получаем нулевую стоимость абстракций (zero cost abstractions). Дело в том, что абстракции обычно не даются нам бесплатно. Объекты надо создавать, где-то хранить, удалять. И чем больше вложенность абстракции, тем дороже по ресурсам это выходит.
В Rust можно декомпозировать абстракции на десятки, сотни, а то и тысячи слоев при необходимости с сохранением все той же производительности, как вообще без какого-либо уровня абстракции. Правила системы владения проверяются на этапе компиляции. Из-за этого мы теряем во времени на компиляцию программы, но выигрываем в стоимости абстракций.
Благодаря отсутствию GC в Rust мы получаем нулевую стоимость абстракций (zero cost abstractions). Дело в том, что абстракции обычно не даются нам бесплатно. Объекты надо создавать, где-то хранить, удалять. И чем больше вложенность абстракции, тем дороже по ресурсам это выходит.
В Rust можно декомпозировать абстракции на десятки, сотни, а то и тысячи слоев при необходимости с сохранением все той же производительности, как вообще без какого-либо уровня абстракции. Правила системы владения проверяются на этапе компиляции. Из-за этого мы теряем во времени на компиляцию программы, но выигрываем в стоимости абстракций.
Ранний возврат ошибок
Ранний возврат ошибок — это подход, при котором функция возвращает ошибку как можно раньше, если ее можно определить. Это облегчает чтение и понимание кода. Вот пример на языке программирования Rust:
В этом примере функция
При раннем возврате негативные сценарии обрабатываются как можно раньше. Это позволяет сократить вложенность выражений и упростить читаемость кода.
Используете ли вы в своей практике «ранний возврат»?
Ранний возврат ошибок — это подход, при котором функция возвращает ошибку как можно раньше, если ее можно определить. Это облегчает чтение и понимание кода. Вот пример на языке программирования Rust:
fn some_function(arg: i32) -> Result<i32, &'static str> {
if arg <= 0 {
return Err("Invalid argument. It should be greater than 0.");
}
// Some computation here...
Ok(result) // Assuming result is the result of the computation
}
В этом примере функция
some_function
возвращает ошибку сразу, как только обнаруживает, что аргумент меньше или равен нулю. Это обеспечивает «ранний возврат» ошибки.При раннем возврате негативные сценарии обрабатываются как можно раньше. Это позволяет сократить вложенность выражений и упростить читаемость кода.
Используете ли вы в своей практике «ранний возврат»?
👍2
Хвостовая рекурсия
Хвостовая рекурсия — это форма рекурсии, в которой рекурсивный вызов становится последней операцией в функции. Хвостовая рекурсия быстрее за счет того, что компилятор может оптимизировать её, заменяя рекурсивный вызов оператором перехода. Это снижает затраты памяти и процессорных вычислений, так как не требуется сохранять промежуточные состояния стека вызовов.
Например, рассмотрим рекурсивную функцию, вычисляющую факториал числа, на языке Rust:
Эта версия функции не является хвостовой рекурсией, поскольку умножение выполняется после рекурсивного вызова.
Теперь давайте преобразуем её в хвостовую рекурсию:
В этом примере мы добавили дополнительную функцию
В обычной рекурсии, операция умножения выполняется после рекурсивного вызова, что создает дополнительную нагрузку на стек вызовов. В хвостовой рекурсии, все вычисления выполняются до рекурсивного вызова. Именно такой подход позволяет компилятору оптимизировать вызов.
Важно отметить, что хвостовая рекурсия не ограничивается только языком Rust. Она широко применима и в других языках программирования, таких как Python, Java, JavaScript, PHP, C++ и др. Хвостовая рекурсия позволяет эффективно использовать системные ресурсы, что особенно важно при работе с большими объемами данных или при реализации сложных алгоритмов.
Хвостовая рекурсия — это форма рекурсии, в которой рекурсивный вызов становится последней операцией в функции. Хвостовая рекурсия быстрее за счет того, что компилятор может оптимизировать её, заменяя рекурсивный вызов оператором перехода. Это снижает затраты памяти и процессорных вычислений, так как не требуется сохранять промежуточные состояния стека вызовов.
Например, рассмотрим рекурсивную функцию, вычисляющую факториал числа, на языке Rust:
fn factorial(n: u64) -> u64 {
match n {
0 => 1,
_ => n * factorial(n - 1),
}
}
Эта версия функции не является хвостовой рекурсией, поскольку умножение выполняется после рекурсивного вызова.
Теперь давайте преобразуем её в хвостовую рекурсию:
fn factorial(n: u64) -> u64 {
fn helper(n: u64, acc: u64) -> u64 {
match n {
0 => acc,
_ => helper(n - 1, acc * n),
}
}
helper(n, 1)
}
В этом примере мы добавили дополнительную функцию
helper
, которая принимает дополнительный аргумент acc
. Этот аргумент используется для накопления результата, и все вычисления теперь выполняются до рекурсивного вызова. Это позволяет компилятору оптимизировать рекурсивный вызов.В обычной рекурсии, операция умножения выполняется после рекурсивного вызова, что создает дополнительную нагрузку на стек вызовов. В хвостовой рекурсии, все вычисления выполняются до рекурсивного вызова. Именно такой подход позволяет компилятору оптимизировать вызов.
Важно отметить, что хвостовая рекурсия не ограничивается только языком Rust. Она широко применима и в других языках программирования, таких как Python, Java, JavaScript, PHP, C++ и др. Хвостовая рекурсия позволяет эффективно использовать системные ресурсы, что особенно важно при работе с большими объемами данных или при реализации сложных алгоритмов.
Немного про Rust
Язык программирования Rust был создан для разработки безопасного и эффективного ПО, позволяя контролировать низкоуровневые детали, при этом обеспечивая безопасность и предотвращение ошибок.
Преимущества Rust включают гарантированную безопасность памяти, минимизацию ошибок, высокую производительность, а также поддержку параллелизма и функционального программирования.
Rust поддерживает объектно-ориентированное программирование (ООП), но делает это иначе, чем большинство традиционных языков ООП. В Rust нет классов, вместо этого он использует структуры и перечисления для создания типов данных, а также имплементаций для добавления методов к типам данных. Это позволяет достичь многих целей ООП, таких как инкапсуляция и полиморфизм, но с большей гибкостью и безопасностью.
В Rust выделять память не нужно вручную, как это происходит в некоторых других языках программирования, например в C и C++. Rust обеспечивает безопасность памяти, используя уникальную систему владения и времени жизни объектов. Это предотвращает распространенные ошибки программирования, такие как двойное освобождение памяти, утечки памяти и доступ к освобожденной памяти. Система владения гарантирует, что каждый объект имеет одного владельца, который отвечает за освобождение памяти, а система времени жизни следит за тем, чтобы ссылки на объект не использовались после его уничтожения.
Язык программирования Rust был создан для разработки безопасного и эффективного ПО, позволяя контролировать низкоуровневые детали, при этом обеспечивая безопасность и предотвращение ошибок.
Преимущества Rust включают гарантированную безопасность памяти, минимизацию ошибок, высокую производительность, а также поддержку параллелизма и функционального программирования.
Rust поддерживает объектно-ориентированное программирование (ООП), но делает это иначе, чем большинство традиционных языков ООП. В Rust нет классов, вместо этого он использует структуры и перечисления для создания типов данных, а также имплементаций для добавления методов к типам данных. Это позволяет достичь многих целей ООП, таких как инкапсуляция и полиморфизм, но с большей гибкостью и безопасностью.
В Rust выделять память не нужно вручную, как это происходит в некоторых других языках программирования, например в C и C++. Rust обеспечивает безопасность памяти, используя уникальную систему владения и времени жизни объектов. Это предотвращает распространенные ошибки программирования, такие как двойное освобождение памяти, утечки памяти и доступ к освобожденной памяти. Система владения гарантирует, что каждый объект имеет одного владельца, который отвечает за освобождение памяти, а система времени жизни следит за тем, чтобы ссылки на объект не использовались после его уничтожения.
❤1👎1
Как компилируется Rust
Компилятор Rust, известный как
1. Парсинг и проверка синтаксиса
Исходный код, который вы пишете, это последовательность символов. Задача лексического анализатора — преобразовать эту последовательность в токены. Токены — это основные единицы, такие как ключевые слова, идентификаторы, литералы и знаки препинания.
Как только код токенизирован, начинает работу синтаксический анализатор. Он использует токены для создания абстрактного синтаксического дерева (abstract syntax tree). AST — это древовидное представление исходного кода, где каждый узел соответствует конструкции в коде.
Например, рассмотрим код на Rust:
AST может представлять
2. Семантический анализ
На этапе семантического анализа работа ведется уже с AST. Этот шаг включает:
Разрешение (Resolving): Компилятор определяет, что означает каждое имя (например, переменная или функция).
Проверка типов (Type checking): Rust обеспечивает безопасность типов, поэтому компилятор проверяет, чтобы, например, вы не пытались добавить строку к целому числу.
Проверка заимствований (Borrow checking): Одной из уникальных особенностей Rust является его проверка заимствования. Это гарантирует соблюдение правил владения и заимствования для ссылок на данные.
3. Высокоуровневое промежуточное представление (High-Level Intermediate Representation)
Затем компилятор преобразует AST в высокоуровневое промежуточное представление (HIR). HIR упрощает AST, упрощая компилятору выполнение конкретных преобразований и оптимизаций. Оно более абстрактно, чем исходный код, но все же тесно с ним связано.
4. Промежуточное представление среднего уровня (Mid-Level Intermediate Representation)
Затем HIR преобразуется в промежуточное представление среднего уровня (MIR). MIR — это более простое, более абстрактное представление вашего кода на Rust. На этом этапе многие продвинутые функции Rust были преобразованы в набор более простых конструкций.
MIR позволяет проводить продвинутые анализы и преобразования потока данных. Именно здесь работает проверка заимствований, которая гарантирует, что ссылки следуют строгим правилам заимствования и владения Rust.
5. Бекенд компиляция (LLVM)
После MIR, предпоследним этапом вступает backend компиляция. Backend отвечает за преобразование MIR в исполняемый код. Это включает:
Преобразование в промежуточное представление LLVM (LLVM IR): Rust использует инфраструктуру компилятора LLVM (возможны и другие бекенды, например rustc-codegen-ssa или rustc-codegen-cranelift). MIR преобразуется в промежуточное представление LLVM (LLVM IR). LLVM IR — это независимое от платформы представление на низком уровне.
Оптимизация: Как только код преобразован в LLVM IR, запускается серия проходов оптимизации для повышения эффективности кода.
Генерация байт кода: Наконец, оптимизированный LLVM IR переводится в машинный код для целевой платформы.
6. Связывание
Как и большинство языков, Rust позволяет разбивать код на несколько файлов и даже связываться с предварительно скомпилированными библиотеками. Последняя стадия компиляции включает в себя связывание всех этих частей вместе, чтобы создать единый исполняемый файл или библиотеку.
Компилятор Rust, известный как
rustc
, проходит через несколько стадий, чтобы преобразовать исходный код на языке Rust в исполняемый файл.1. Парсинг и проверка синтаксиса
Исходный код, который вы пишете, это последовательность символов. Задача лексического анализатора — преобразовать эту последовательность в токены. Токены — это основные единицы, такие как ключевые слова, идентификаторы, литералы и знаки препинания.
Как только код токенизирован, начинает работу синтаксический анализатор. Он использует токены для создания абстрактного синтаксического дерева (abstract syntax tree). AST — это древовидное представление исходного кода, где каждый узел соответствует конструкции в коде.
Например, рассмотрим код на Rust:
fn main() {
let x = 5;
}
AST может представлять
fn
, main
, ()
, {
, let
, x
, =,
5
и }
как отдельные узлы, формируя дерево, которое отражает их иерархические отношения.2. Семантический анализ
На этапе семантического анализа работа ведется уже с AST. Этот шаг включает:
Разрешение (Resolving): Компилятор определяет, что означает каждое имя (например, переменная или функция).
Проверка типов (Type checking): Rust обеспечивает безопасность типов, поэтому компилятор проверяет, чтобы, например, вы не пытались добавить строку к целому числу.
Проверка заимствований (Borrow checking): Одной из уникальных особенностей Rust является его проверка заимствования. Это гарантирует соблюдение правил владения и заимствования для ссылок на данные.
3. Высокоуровневое промежуточное представление (High-Level Intermediate Representation)
Затем компилятор преобразует AST в высокоуровневое промежуточное представление (HIR). HIR упрощает AST, упрощая компилятору выполнение конкретных преобразований и оптимизаций. Оно более абстрактно, чем исходный код, но все же тесно с ним связано.
4. Промежуточное представление среднего уровня (Mid-Level Intermediate Representation)
Затем HIR преобразуется в промежуточное представление среднего уровня (MIR). MIR — это более простое, более абстрактное представление вашего кода на Rust. На этом этапе многие продвинутые функции Rust были преобразованы в набор более простых конструкций.
MIR позволяет проводить продвинутые анализы и преобразования потока данных. Именно здесь работает проверка заимствований, которая гарантирует, что ссылки следуют строгим правилам заимствования и владения Rust.
5. Бекенд компиляция (LLVM)
После MIR, предпоследним этапом вступает backend компиляция. Backend отвечает за преобразование MIR в исполняемый код. Это включает:
Преобразование в промежуточное представление LLVM (LLVM IR): Rust использует инфраструктуру компилятора LLVM (возможны и другие бекенды, например rustc-codegen-ssa или rustc-codegen-cranelift). MIR преобразуется в промежуточное представление LLVM (LLVM IR). LLVM IR — это независимое от платформы представление на низком уровне.
Оптимизация: Как только код преобразован в LLVM IR, запускается серия проходов оптимизации для повышения эффективности кода.
Генерация байт кода: Наконец, оптимизированный LLVM IR переводится в машинный код для целевой платформы.
6. Связывание
Как и большинство языков, Rust позволяет разбивать код на несколько файлов и даже связываться с предварительно скомпилированными библиотеками. Последняя стадия компиляции включает в себя связывание всех этих частей вместе, чтобы создать единый исполняемый файл или библиотеку.
✍1