Перейти к основному содержимому

Синтаксис и пунктуация в 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, в third30, а 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 и владеет своим буфером.