1. Основы Rust: синтаксис, переменные и базовая типизация
Основы Rust: синтаксис, переменные и базовая типизация
В 2006 году сотрудник Mozilla Грэйдон Хор начал работу над проектом, который должен был решить фундаментальную дилемму системного программирования: как обеспечить производительность C++ без его фатальной уязвимости к ошибкам работы с памятью. Сегодня Rust — это не просто «еще один язык», а технологический стандарт для создания облачных инфраструктур, браузерных движков и блокчейн-платформ. Если в других языках вы боретесь с программой во время выполнения (runtime), то в Rust вы ведете переговоры с компилятором. Этот диалог начинается с понимания того, как язык структурирует данные и управляет их изменчивостью.
Философия синтаксиса и структура программы
Rust часто называют «выразительным» языком. Это означает, что почти любая конструкция в нем является выражением (expression), которое возвращает значение, а не просто инструкцией (statement), которая выполняет действие. Это фундаментальное отличие от языков семейства C определяет стиль написания кода.
Простейшая программа на Rust всегда начинается с функции main.
Здесь fn — ключевое слово для объявления функции, а восклицательный знак в println! указывает на то, что это макрос, а не обычная функция. Макросы в Rust работают на этапе компиляции, генерируя код, который проверяет типы аргументов и форматирование строк до того, как программа будет запущена. Это первый эшелон защиты: вы не сможете передать неверное количество аргументов в строку форматирования, так как компилятор остановит сборку.
Блоки кода как выражения
В Rust блок кода в фигурных скобках {} может возвращать значение. Последняя строка блока, если она написана без точки с запятой, становится результатом всего блока.
Если вы поставите точку с запятой после y + z, выражение превратится в инструкцию, и блок вернет «пустой» тип (), называемый unit-типом. Эта особенность позволяет писать лаконичный код, избегая лишних временных переменных.
Переменные и концепция неизменяемости
По умолчанию все переменные в Rust неизменяемы (immutable). Это сознательное проектное решение, направленное на упрощение параллельного программирования и предотвращение побочных эффектов.
Чтобы сделать переменную изменяемой, необходимо явно использовать ключевое слово mut:
Зачем такая строгость? В системном программировании неконтролируемое изменение состояния — главный источник багов в многопоточной среде. Когда вы видите let, вы гарантированно знаете, что значение не изменится ниже по коду. Если же вы видите let mut, это сигнал: «Внимание, здесь состояние трансформируется».
Затенение переменных (Shadowing)
Rust позволяет объявлять новую переменную с тем же именем, что и у предыдущей. Это называется затенением.
В данном примере мы сначала создали строковую переменную, а затем «затенили» её числовой переменной. Это отличается от mut, потому что:
let mut).Затенение часто применяется при трансформации данных, когда нам не нужно хранить промежуточные состояния вроде input_str, input_trimmed, input_int. Мы просто используем одно имя input на каждом этапе обработки.
Система типов: статика и строгий контроль
Rust — язык со статической типизацией, но благодаря выводу типов (type inference), программисту не всегда нужно указывать их явно. Компилятор анализирует использование переменной и «догадывается», какой тип ей нужен. Однако в сигнатурах функций и при неоднозначности явное указание типа обязательно.
Скалярные типы
i8, i16, i32, i64, i128 и isize.
- Беззнаковые: u8, u16, u32, u64, u128 и usize.
Типы isize и usize зависят от архитектуры компьютера (32 или 64 бита) и используются в основном для индексации коллекций. Нюанс переполнения: В режиме отладки (debug) Rust проверяет целочисленное переполнение и вызывает панику (аварийную остановку). В режиме релиза (--release) проверки отключаются ради скорости, и происходит циклическое переполнение (wrapping). Для специфического поведения существуют методы вроде wrapping_add или checked_add.
f32 (одинарная точность) и f64 (двойная точность, стандарт по умолчанию).bool со значениями true и false. Занимает 1 байт.char. В отличие от C, где char — это 1 байт, в Rust char занимает 4 байта и представляет собой Scalar Value стандарта Unicode. Это позволяет хранить в одной переменной char любой символ: от латиницы до эмодзи и иероглифов.Составные типы
Константы и статические переменные
Помимо неизменяемых переменных, в Rust есть константы (const). Различия между ними критичны для понимания работы памяти:
const всегда требует указания типа.const может быть объявлена в любой области видимости, включая глобальную.Существуют также статические переменные (static), которые представляют собой фиксированный адрес в памяти. В отличие от констант, они не копируются, а живут в сегменте данных программы на протяжении всего времени её работы.
Функции и поток управления
Функции в Rust используют «змеиный регистр» (snake_case). Как уже упоминалось, функции возвращают значение последнего выражения.
Если вы хотите выйти из функции раньше, используется ключевое слово return.
Условные конструкции if
Поскольку if — это выражение, его можно использовать в правой части оператора let.
Важно: типы в обеих ветках if и else должны совпадать. Rust не допустит, чтобы в одной ситуации переменная стала числом, а в другой — строкой, так как тип переменной должен быть определен на этапе компиляции.
Циклы: loop, while и for
loop можно возвращать значение через break.Глубокое погружение: память и типы данных
Чтобы понять, почему Rust требует такой строгости в типах и неизменяемости, нужно рассмотреть, как данные располагаются в памяти.
Стек против Кучи
String). При помещении данных в кучу операционная система ищет свободное место, помечает его как занятое и возвращает указатель (адрес в памяти).В Rust указатель на данные в куче сам по себе является данными фиксированного размера и хранится в стеке. Когда переменная выходит из области видимости, Rust автоматически очищает память в куче. Это основа концепции владения, которую мы детально разберем в следующей главе, но фундамент закладывается именно здесь: через понимание типов и их размеров.
Строки: String vs &str
Работа со строками в Rust часто сбивает новичков с толку. Существует два основных типа:
Разница в том, что String — это структура, содержащая указатель, длину и емкость, а &str — это «вид» на часть строки, состоящий только из указателя и длины.
Типизация как инструмент проектирования
Система типов Rust позволяет выражать инварианты программы на уровне компиляции. Рассмотрим пример с usize. Если вы проектируете функцию, принимающую индекс массива, использование usize гарантирует, что индекс не будет отрицательным. В языке вроде C++ вам пришлось бы использовать int и проверять ` вручную, либо использовать unsigned int и следить за приведением типов. В Rust типы бесшовны, но строги.
Явное приведение типов (Casting)
Rust не выполняет неявное приведение типов (implicit coercion) для примитивов. Вы не можете сложить i32 и u32 просто так.
Это защищает от трудноуловимых ошибок потери точности или знака, которые часто встречаются в системном коде на C.
Практический пример: вычисление n-го числа Фибоначчи
Для закрепления основ синтаксиса и типизации напишем небольшую утилиту. Она демонстрирует использование циклов, условий и типов данных для работы с потенциально большими числами.
В этом примере:
, так как числа Фибоначчи растут экспоненциально. — это итератор диапазона, включающий конечное значение. в цикле for указывает на то, что нам не нужно значение счетчика цикла, мы просто хотим повторить действие.Обработка числовых границ и безопасность
При работе с системными инструментами важно учитывать границы типов. В Rust есть встроенные методы для безопасной арифметики. Например, если мы боимся, что число Фибоначчи выйдет за пределы u128:
Хотя глубокая обработка ошибок (тип Option и Result) — тема будущих глав, важно понимать, что базовая типизация Rust тесно переплетена с безопасностью. Компилятор заставляет вас думать о граничных случаях еще на этапе написания кода.
Идиоматический Rust: советы по стилю
, типы (структуры, перечисления) — PascalCase, константы — SCREAMING_SNAKE_CASE. для целых чисел, если нет веских причин использовать другой размер. Это наиболее эффективно на большинстве современных процессоров.. Добавляйте mut только тогда, когда компилятор сообщит о необходимости изменения. Это минимизирует область видимости изменяемого состояния.Rust — это инструмент, который вознаграждает за дисциплину. Поначалу строгость типов и неизменяемость могут казаться ограничивающими, но со временем они становятся «вторым пилотом», который берет на себя проверку тривиальных, но опасных ошибок. Понимание того, как данные лежат в памяти и как типизация защищает доступ к ним — это первый шаг к созданию высокопроизводительных систем, которыми славится этот язык.