Синтаксис и пунктуация в Rust
Названия знаков по-английски и по-русски: Знаки препинания и символы в IT.
О чём эта статья
"Азбука" знаков в Rust: кавычки для строк и char, ;, { }, макросы с !, атрибуты #[]. Справочник по пунктуации — когда компилятор ругается на кавычки или точку с запятой.
Дальше: типы и владение → управление и match.
Синтаксис и пунктуация в Rust
Двойные кавычки и строки
Двойные кавычки (") служат маркером начала и конца строковых литералов. Компилятор Rust интерпретирует текст внутри таких кавычек как последовательность байтов, представляющую собой строку типа &str (строка-ссылка) или String (владение строкой).
let name = "Alice";
let greeting = "Hello, world!";
Разбор:
letобъявляет неизменяемую переменную: имя связывается со значением один раз.nameиgreetingполучают тип&str(строковый срез), потому что используются строковые литералы.- Литералы
"Alice"и"Hello, world!"хранятся в сегменте бинарника, а переменные содержат ссылки на эти данные. - Переменные не владеют строкой в куче, поэтому нет аллокации и освобождения памяти через
drop.
Строки, заключенные в двойные кавычки, могут содержать специальные символы, такие как перенос строки \n, табуляцию \t или обратный слэш \\. Компилятор автоматически обрабатывает эти экранированные последовательности при создании строкового объекта. Строковые литералы являются неизменяемыми по умолчанию и хранятся в сегменте памяти кода.
let escaped = "Строка\nс переносом\tи табуляцией";
let raw = r"C:\Users\dev\project"; // сырой литерал: обратные слэши не экранируются
let owned = String::from("динамическая строка");
let slice: &str = &owned; // срез на данные String
Разбор:
\nи\tв обычной строке превращаются в управляющие символы при компиляции.- Префикс
r"..."создает raw string: удобно для путей Windows и regex. String::fromвыделяет изменяемую строку в куче; владелец — переменнаяowned.&ownedдает&strбез копирования содержимого, только ссылку на буфер.
Одинарные кавычки и символы
Одинарные кавычки (') предназначены исключительно для обозначения литералов типа char. Символ внутри одинарных кавычек представляет собой один Unicode скалярный код. Размер такого символа всегда фиксирован и составляет 4 байта в памяти.
let letter = 'A';
let emoji = '🚀';
let digit = '7';
Разбор:
- Литералы в одинарных кавычках имеют тип
char, а не&str. charв Rust хранит один Unicode scalar value (4 байта), поэтому эмодзи'🚀'корректен как одиночный символ.letterиdigitтакжеchar, даже если визуально похожи на "строки длины 1".- Такой тип удобен для посимвольной обработки, но не заменяет строку для текста.
В отличие от строк, char — это один Unicode scalar value (4 байта), а не "графема" в смысле пользователя: составные эмодзи или символы с диакритикой могут занимать несколько char. Для текста по смыслу используют &str / String.
Апострофы и их отсутствие
Символ апострофа (’), часто используемый в естественном языке для сокращений или притяжательных падежей, не имеет синтаксического значения в языке программирования Rust. Использование этого знака в коде приводит к ошибке компиляции, так как компилятор ожидает строго определенные грамматические конструкции.
Разработчики должны использовать только стандартные кавычки (" и ') и другие знаки препинания, описанные в спецификации языка. Попытка внедрить типографские варианты знаков, такие как правый апостроф, нарушит структуру программы.
Точка доступа к членам
Точка (.) является оператором доступа к полям структур и вызова методов объектов. Этот символ связывает экземпляр структуры или значение со своими внутренними компонентами.
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 10, y: 20 };
println!("X coordinate: {}", p.x);
println!("Y coordinate: {}", p.y);
Разбор:
struct Pointобъявляет пользовательский тип с двумя полямиi32.Point { x: 10, y: 20 }создает экземпляр структуры через инициализацию по именам полей.- Выражения
p.xиp.yиспользуют оператор.для доступа к полям. - Макрос
println!форматирует строку:{}заменяются значениями полей во время выполнения.
В примере выше выражение p.x обращается к полю x экземпляра p. Аналогично работает вызов методов, например string.len(), где точка отделяет имя переменной от имени метода. Без точки компилятор не сможет определить контекст обращения к данным.
Запятая разделения элементов
Запятая (,) выполняет функцию разделителя в списках элементов. Она используется при объявлении массивов, перечислении аргументов функций, определении полей структур и разделении переменных в присваивании.
let numbers = [1, 2, 3, 4, 5];
fn calculate(a: i32, b: i32, c: i32) -> i32 {
a + b + c
}
let result = calculate(10, 20, 30);
Разбор:
- В массиве
[1, 2, 3, 4, 5]запятая разделяет элементы одинакового типа. - В сигнатуре
fn calculate(a — i32, b: i32, c: i32)запятые разделяют параметры функции. - В вызове
calculate(10, 20, 30)запятые отделяют аргументы при передаче. -> i32задает тип возвращаемого значения, а телоa + b + cвозвращает результат как выражение.
В массиве запятые отделяют значения друг от друга. В сигнатуре функции они разделяют параметры. При объявлении нескольких переменных в одной строке запятая также служит разделителем. Отсутствие запятой между элементами приведет к синтаксической ошибке.
Точка с запятой завершение выражений
Точка с запятой (;) является обязательным завершающим знаком для большинства инструкций в блоке кода. Она сообщает компилятору о том, что выражение завершено и результат может быть обработан или проигнрирован.
let x = 5;
let y = 10;
let sum = x + y;
println!("The sum is: {}", sum);
Разбор:
- Каждая инструкция завершается
;, что явно отделяет ее от следующей. let sum = x + y;вычисляет выражениеx + yи сохраняет результат в новой переменной.- Вызов
println!(...)тоже завершен;, поэтому используется как действие (side effect), а не возвращаемое значение блока. - Если убрать
;у последнего выражения в блоке, блок начнет возвращать его значение.
В Rust точка с запятой преобразует выражение в инструкцию. Выражение без точки с запятой возвращает значение, которое может быть использовано дальше. Инструкция с точкой с запятой не возвращает значение (или возвращает единицу ()). Пропуск этой точки после объявления переменной или вызова функции вызовет ошибку синтаксиса.
let score = 87;
let grade = if score >= 90 {
"A"
} else if score >= 75 {
"B"
} else {
"C"
};
println!("Оценка: {}", grade);
Разбор:
if/else if/elseздесь — выражение, а не только оператор ветвления.- Ветки возвращают значения одного типа (
&str), поэтому результат можно присвоить вgrade. - После всего
if-выражения стоит;, потому что присваиваниеlet grade = ...— это инструкция. - Такой стиль заменяет тернарный оператор
?:из C-подобных языков.
Подчеркивание и соглашения именования
Нижнее подчеркивание (_) в Rust имеет несколько специфических применений, связанных с управлением вниманием компилятора и структурой кода.
Использование _name указывает на то, что переменная объявлена, но её значение не будет использоваться в дальнейшем. Это предотвращает генерацию предупреждений компилятора о неиспользуемых переменных.
let _unused_variable = "This value will not be used";
Разбор:
- Префикс
_в имени (_unused_variable) сообщает компилятору, что переменная может не использоваться. - Тип выводится как
&str, потому что присваивается строковый литерал. - Значение остается корректно созданным, но предупреждение
unused variableподавляется. - Это полезно при прототипировании и временной подготовке кода.
Компилятор игнорирует предупреждения для идентификаторов, начинающихся с подчеркивания, если они не используются. Однако это соглашение не влияет на приватность доступа. Приватность в Rust определяется ключевым словом pub или его вариациями (pub(crate)), а не наличием подчеркивания.
Самостоятельное использование одиночного подчеркивания (_) означает полное игнорирование соответствующего элемента в деструктуризации или сопоставлении с образцом.
let (first, _, third) = (10, 20, 30);
// Значение 20 игнорируется
Разбор:
- Слева используется деструктуризация кортежа: значения раскладываются по шаблону.
_— специальный плейсхолдер, который "поглощает" элемент без создания именованной переменной.- В
firstпопадет10, вthird—30, а20будет намеренно проигнорировано. - Этот прием уменьшает шум, когда часть данных не нужна в текущей логике.
Здесь переменная _ заменяет второе значение кортежа, так как оно не требуется программе.
Числовые литералы и читаемость
Подчеркивание допустимо внутри числовых литералов для улучшения визуального восприятия больших чисел. Разделители помогают быстро оценить порядок величины числа.
let million = 1_000_000;
let gigabyte = 1_073_741_824;
let binary_value = 0b1010_1010;
Разбор:
- Подчеркивания в литералах (
1_000_000) улучшают читаемость и не влияют на реальное значение. 0b1010_1010— двоичный литерал; префикс0bуказывает основание 2.- Компилятор удаляет визуальные разделители на этапе парсинга, оставляя обычное число.
- Такой стиль особенно полезен для битовых масок, размеров памяти и больших констант.
Компилятор полностью игнорирует подчеркивания внутри чисел при компиляции. Результат вычислений идентичен результату при отсутствии разделителей. Такой подход повышает читаемость кода и снижает риск опечаток при вводе длинных констант.
Плейсхолдер в сопоставлении с образцом
В конструкции match символ _ выступает в роли плейсхолдера, который соответствует любому значению, не попавшему в предыдущие ветви условия. Это аналог блока else в условных операторах.
let value = 5;
match value {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Something else or default"),
}
Разбор:
matchсравниваетvalueс шаблонами сверху вниз.- Ветки
1,2,3обрабатывают конкретные значения, а_выступает как "все остальные случаи". - Такой подход делает разбор исчерпывающим: компилятор требует покрыть все варианты.
- Макрос
println!в каждой ветке показывает выбранный путь выполнения.
Если значение value равно 1, 2 или 3, выполняется соответствующая ветка. Если значение другое, выполняется блок под _. Это обеспечивает обработку всех возможных случаев и предотвращает ошибки времени выполнения из-за непредвиденных значений.
Побитовое ИЛИ и замыкания
Символ вертикальной черты (|) выполняет две различные роли в зависимости от контекста использования.
Первая роль — побитовое ИЛИ. Оператор применяет действие к каждому биту целочисленных операндов.
let x = 5 | 3; // Результат 7 (101 | 011 = 111)
Разбор:
|здесь интерпретируется как побитовое ИЛИ для целых чисел.- Двоичные представления
5(101) и3(011) объединяются поразрядно. - В каждом бите результат равен 1, если хотя бы один из исходных битов равен 1.
- Итог
111в двоичной форме равен7в десятичной.
Второй ролью является определение границ параметров замыкания (closures). Параметры замыкания оборачиваются в вертикальные черты.
let double = |x: i32| x * 2;
let result = double(5); // 10
Разбор:
|x: i32|— список параметров замыкания; синтаксис похож на анонимную функцию.- Тело
x * 2— выражение, автоматически возвращаемое безreturn. doubleполучает тип замыкания, который можно вызывать как функцию.- Вызов
double(5)подставляет аргумент и возвращает10.
Выражение |x| обозначает начало списка параметров замыкания и его конец. Внутри этих черт можно указать типы параметров и их количество. Если параметров нет, используют пустые черты ||.
Логическое ИЛИ
Две вертикальные черты (||) представляют собой оператор логического ИЛИ. Он применяется к булевым значениям и возвращает истину, если хотя бы одно из условий истинно.
let a = true;
let b = false;
if a || b {
println!("At least one condition is true");
}
Разбор:
aиbимеют типbool, поэтому выражениеa || bявляется логическим ИЛИ.- Оператор
||использует short-circuit: еслиa == true, проверкаbможет не выполняться. - Условие
ifвыполняет блок только при истинном результате выражения. - Внутри блока вызывается
println!, что демонстрирует успешное прохождение условия.
Этот оператор отличается от побитового ИЛИ тем, что работает с логическими типами, а не с битами чисел. Он часто используется в условиях циклов и условных операторов для проверки нескольких критериев одновременно.
Сравнение операций с похожими символами
Важно различать операции с одним и двумя знаками. Одиночная черта | для битовых операций и параметров замыкания. Двойная черта || исключительно для логики. Путаница между этими операторами приводит к ошибкам логики программы или ошибкам компиляции.
Компилятор Rust строго проверяет контекст использования каждого знака. Использование | вместо || в условии if вызовет ошибку, так как ожидается булев тип, а получен целый. Использование || вместо | в побитовой операции также невозможно из-за разницы в семантике.
let is_ready = true;
let has_token = false;
if is_ready && has_token {
println!("Можно выполнять запрос");
}
Разбор:
&&— логическое И: блок выполнится только если оба операндаtrue.- При
is_ready == falseправая часть (has_token) не вычисляется (short-circuit). - В отличие от
|, здесь требуются именноbool, а не целые числа. - Паттерн часто используют для проверки нескольких предусловий перед действием.
Фигурные скобки, макросы и атрибуты
Фигурные скобки { } задают блок — область видимости и единицу кода, которая может быть выражением. Макросы вызываются с ! после имени (println!, vec!, format!). Атрибуты #[] и #![ ] добавляют метаданные компилятору (например, #[derive(Debug)]).
Код ITЗагрузка примера кода…
Разбор:
#[derive(Debug)]— атрибут: автоматически реализует трейтDebugдляConfig.- Блок
{ let base = 21; base * 2 }создает локальную переменную и возвращает результат. println!— макрос (обратите внимание на!), а не обычная функция.{:?}в формате печатает структуру черезDebug,{}— обычное значениеdoubled.
let items = vec![1, 2, 3];
let formatted = format!("count={}", items.len());
Разбор:
vec!— макрос создания вектора с начальными элементами.format!возвращает новуюString, не печатая в консоль.items.len()— метод коллекции, вызывается через точку.- Результат
formattedимеет типStringи владеет своим буфером.