Основы Rust: синтаксис, макросы и структуры данных, сравнение с языком C++

Основы Rust: синтаксис и структуры данных

Никита Алексеев
Никита Алексеев Разработчик
13 марта 2026

Обзор ключевых особенностей языка Rust: его синтаксиса, работы с макросами и структурами данных. Также сравним подходы Rust и C++ к управлению памятью, производительности и безопасности.

Изображение записи

Rust – современный язык программирования, созданный ориентиром на безопасность, скорость и контроль над ресурсами. За последние годы он стал одним из главных инструментов для системных разработчиков, инженеров микросервисов и создателей высоконагруженных сервисов.

Где используется Rust

Rust применяется в задачах, где критически важны надежность, безопасность и производительность. Среди примеров использования: 

  • системное ПО и встроенные решения;
  • сетевые приложения и движки браузеров (например, отдельные части Firefox написаны на Rust);
  • высоконагруженные серверные сервисы и прокси (например, в Cloudflare активно используется Rust для сетевых компонентов и инструментов мониторинга).

Благодаря строгой системе типов и механизму владения памятью Rust популярен и в области блокчейн‑разработки. На нем реализованы, например, части экосистем Polkadot и Solana, где особенно важно предотвращать уязвимости и ошибки обращения к памяти. 
Rust также применяется для создания системных утилит, библиотек и сервисов, где требуется гарантированно безопасная работа с данными и ресурсами, а также игровых серверов. К слову, об играх: в Академии Selectel вы можете найти много интересных материалов о геймдеве, игровых новинках, модах и не только.

Синтаксис Rust

Синтаксис Rust только издалека напоминает C и C++, но определения переменных и функций, а также типизация переменных довольно уникальны.

Переменные и операторы

Переменные в Rust объявляются с помощью ключевого слова let. По умолчанию они неизменяемые (immutable) — это значит, что после присваивания значение нельзя поменять:


      let x = 5;
// x = 6; // будет ошибка компиляции

Если требуется изменить значение переменной, ее нужно объявить как изменяемую с помощью ключевого слова mut. Такой подход помогает минимизировать непреднамеренные изменения состояния программы.


      let mut y = 10;
y = 15;

Rust также поддерживает затенение (shadowing) — повторное объявление переменной с тем же именем:


      let x = 5;
// Новая переменная x
let x = x + 1;
let x = “new &str variable”;

Это удобно при пошаговых преобразованиях значений или изменении типа данных.Rust содержит стандартный набор арифметических операторов: +, -, *, /, %.


      let a = 10;
let b = 3;
let sum = a + b;
let diff = a - b;
let mul = a * b;
let div = a / b;
let rem = a % b;

Для работы с логическими выражениями используются операторы &&, || и !:


      let is_valid = true;
let is_ready = false;

let result = is_valid && !is_ready;

А сравнение выполняется при помощи ==, !=, <, >, <=, >=:


      let x = 5;
let y = 10;

let check1 = x == y;
let check2 = x != y;
let check3 = x < y;
let check4 = x > y;

Важно помнить: оператор = всегда означает присваивание, а не сравнение. В Rust почти все является выражением, поэтому можно сохранять результат вычислений прямо в переменные.

Условный оператор if

Синтаксис условных операторов в Rust интуитивно понятен и похож на другие языки — есть блок if, а также необязательный блок else.


      if x > 10 {
    println!("Больше 10");
} else {
    println!("10 или меньше");
}

Отличие в том, что условие должно быть логическим значением (bool). Автоматическое преобразование чисел в логический тип (как в C или C++) не допускается.

Циклы

В Rust есть три основных вида циклов: loop, while и for. Они используются для многократного выполнения блока кода.
С циклами for и while мы встречаемся во многих языках и здесь они работают аналогично. for перебирает объекты, по которым можно итерироваться — например, диапазоны:


      for i in 0..5 {
    println!("{}", i);
}
// Выведет 0 1 2 3 4

В свою очередь while выполняет блок, пока условие истинно:


      let mut n = 3;
while n > 0 {
    println!("{}", n);
    n -= 1;
}
// Выведет 3 2 1

loop создает бесконечный цикл, прерываемый вручную:


      let mut x = 0;
loop {
    x += 1;
    if x == 5 {
        break;
    }
}
// x = 5

В последнем примере выполнение прерывается с помощью break, когда значение переменной достигает пяти. Ключевые слова break и continue работают в Rust аналогично другим языкам: первое прерывает цикл, второе — переходит к следующей итерации.

Функции

Объявить функцию можно с помощью ключевого слова fn:


          x += 1;
    if x == 5 {
        break;
    }
}
// x = 5

В последнем примере выполнение прерывается с помощью break, когда значение переменной достигает пяти. Ключевые слова break и continue работают в Rust аналогично другим языкам: первое прерывает цикл, второе — переходит к следующей итерации.


      fn sum(a: i32, b: i32) -> i32 {
   let c = a + b;
   return c
}

Тип возвращаемого значения указывается после стрелки ->. Если функция ничего не возвращает, стрелку и тип можно опустить.

В Rust также можно не писать return, если возвращаемое значение находится на последней строке без точки с запятой — тогда оно автоматически считается результатом функции:


      fn sum(a: i32, b: i32) -> i32 {
   a + b
}

Параметры функции объявляются в формате имя: тип, например a: i32. Это делает сигнатуру функции читаемой и строгой.

Структуры данных и примитивные типы 

В Rust присутствуют есть знакомый по другим языкам набор примитивных типов.

Целые числа. Для целых чисел используются знаковые и беззнаковые типы: i8, i16, i32, i64, i128 и u8, u16, u32, u64, u128. Необходимый размер и наличие знака задаются прямо в типе: i32 — 32-битное целое со знаком, u64 — 64-битное беззнаковое целое.​

Числа с плавающей точкой. Для вещественных чисел используются типы f32 и f64.​ Они подходят для вычислений с дробными значениями, при этом f64 чаще используется по умолчанию благодаря большей точности.​

Булевы значения. Тип bool принимает два значения: true и false и используется во всех логических выражениях и условиях.

Символы и строки

Символ в Rust представлен типом char, строка — String или &str. Строки могут хранить текст в формате UTF-8.


      let char = ‘c’;
let slice = “hello”;
let string = String::from(“hello!”);

Массивы

Массив в Rust обладает фиксированным размером. Он задается при объявлении и не меняется:


      let arr = [1, 2, 3];

Векторы

Вектор — это обыкновенный динамический массив. В нем можно добавлять и удалять элементы, а также проводить многие другие операции — например, изменять размер, обрезать вектор, вставлять элемент в конкретную позицию:


      let mut v = Vec::new();
v.push(10);
v.push(20);
println!(“len: {}”, v.len()); // 2
println!(“first: {}”, v[0]); // 10

Match-выражения

Конструкция match проверяет, соответствует ли значение заданному паттерну (pattern-matching). Как только значение будет удовлетворять паттерну, будет выполнен соответствующий код.


      match value {
    1 => println!("Один"),
    2 => println!("Два"),
    _ => println!("Другое значение"),
}

В примере паттернами выступают как значения 1 и 2, так и универсальный паттерн _, который подходит под любое значение.

Перечисления

Перечисления (enum) очень часто используются в языке и позволяют задать набор возможных вариантов значения:


      enum Language {
    Rust,
    Python,
    Java
}

Объект данного типа принимает одно из перечисленных значений — его можно легко проверить через pattern matching, который мы рассмотрели ранее:


      match l {
    Rust => println!(“I’m Rust!”),
    Python => println!(“I’m Python!”),
    Java => println!(“I’m Java!”),
}

Структура вместо класса

В Rust нет классов в привычном для C++ или Java смысле. Вместо них используются структуры (struct) и реализации (impl):


      struct User {
    name: String,
    age: u32,
}

impl User {
   pub fn new(name: String, age: u32) -> User {
        User { name, age }
    }

   pub fn hello(&self) {
        println!(“My name is {}, I’m {}!”, self.name, self.age)
    }
}

В этом примере мы определяем структуру пользователя (User) с полями возраста и имени, а в блоке impl можем написать функции для этой структуры. 

По умолчанию поля структуры приватны, а определяются они как параметры функции — сначала имя поля, а затем тип после двоеточия. Создать структуру можно через фигурные скобки, указав значение для каждого поля, однако можно определить функцию new, которая будет семантически похожа на конструктор.

Пример использования структуры:


      let user = User::new(String::from(“Mike”), 22);
user.hello();

Владение и ссылки

В Rust при присваивании значений, владеющих ресурсами (например, String), происходит перемещение, а не копирование:


      let a = String::from(“hello!”);
let b = a;
println!(“{a}”) // «a» больше нельзя использовать

После перемещения переменная a становится недействительной, так как право владения строкой передано в b. Если нужно сохранить оба значения, можно явно создать копию:


      let b = a.clone();
println!(“{a} {b}”) // Можно использовать обе переменные

Однако нам не всегда нужно делать копии если мы, например, используем данные только для чтения или они слишком тяжелые для копирования. Здесь нам могут помочь ссылки. Они обозначаются через & и позволяют работать с данными без копирования:


      let s = String::from("Rust");
let r = &s;
println!(“{s} {r}”); 

Ссылка &s заимствует данные у переменной s, не забирая владения. Это основной механизм безопасной работы с памятью в Rust, гарантирующий, что данные не будут изменены или удалены, пока существует активная ссылка на них.

Управление памятью в Rust

Помимо обычных (неизменяемых) ссылок в Rust есть мутабельные (mut), позволяющие менять значение:


      let mut x = 10;
let r = &mut x;
*r = 20;

println!("{}", x); // 20

Ключевое правило: в один момент времени может существовать либо множество неизменяемых ссылок, либо одна мутабельная ссылка на одни и те же данные, но не одновременно. Это часть общей модели владения и заимствования памяти в Rust. За ее соблюдение отвечает специальный компонент компилятора — borrow checker. Это «охранник», который анализирует и мониторит, кто владеет данными, где они живут и кто к ним обращается. Также он проверяет, чтобы один и тот же фрагмент памяти не меняли сразу в нескольких местах, а к данным, которые уже «пропали», обратиться было невозможно.

Три основных принципа borrow checker можно сформулировать так:

  • каждый элемент данных обладает владельцем;
  • при выходе владельца из области видимости память освобождается;
  • нельзя иметь несколько изменяемых ссылок одновременно.

Проще говоря, borrow checker не дает написать код, который может сломаться из-за неправильной работы с памятью, и ловит такие ошибки еще до запуска программы.

Макросы в Rust

В статье мы часто использовали конструкцию println!. Восклицательный в конце знак показывает, что это не функция, а макрос.

Макросы в Rust — это механизм генерации кода на этапе компиляции. Они позволяют писать меньше шаблонного кода и создавать собственные синтаксические конструкции. Ключевое отличие макросов от функций в том, что они работают не со значениями, а с фрагментами кода.

Рассмотрим еще несколько популярных макросов:


      let s = format!("{} + {} = {}", 2, 3, 5); // Создаст строку с текущим форматом
let v1 = vec![1, 2, 3];    // на выходе получим вектор с значениями  [1, 2, 3]
assert_eq!(a, b);  // Проверяет что значения равны иначе выходит из программы

На практике макросы широко применяются для логирования, тестирования, сериализации и автоматизации повторяющихся операций.

Обработка ошибок

В Rust нет исключений в привычном для C++ или Java виде. Вместо этого для ошибок и отсутствия значения используются перечисления Result и Option:


      fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Деление на ноль"))
    } else {
        Ok(a / b)
    }
}

Тип Result<T, E> означает «либо успешное значение T, либо ошибка E». Вызывающая сторона обязана явно обработать оба варианта, например через match:


      match divide(4, 2) {
    Ok(answer) => println!("Answer {answer}"),
    Err(s) => eprintln!(“Error: {s}”),
}

Такой подход обязует разработчика явно обработать ошибку, что делает код более надежным и предсказуемым по поведению.

Сравнение Rust и C++

Rust и C++ – оба системные языки программирования, которые позволяют работать с низкоуровневыми ресурсами и писать высокопроизводительные программы. Рассмотрим их ключевые отличия.

Безопасность. В Rust безопасность работы с памятью включена по умолчанию. Компилятор проверяет ссылки, владение данными и предотвращает утечки. В C++ разработчик сам отвечает за корректное управление памятью, что дает больше свободы, но повышает риск ошибок.

Система типов. Rust использует строгую систему типов. Например, нельзя случайно передать число вместо строки или создать две изменяемые ссылки на один объект. В C++ типы проверяются, но часть ошибок проявляется только во время работы программы.

Оптимизация и производительность. C++ позволяет тонко управлять ресурсами и иногда писать более эффективный код, если программист обладает хорошим опытом. Rust дает немного меньше прямого контроля, но компилятор сам оптимизирует использование памяти и потоков, предотвращая ошибки.

Чем может помочь Selectel

Selectel предоставляет инфраструктуру для запуска и сопровождения проектов на Rust.​ Облачные серверы подойдут для сборки, тестирования и деплоя сервисов на Rust, позволяя сконфигурировать окружение под высокую нагрузку и обеспечить стабильный доступ к приложениям.

Для команды разработчиков это удобный способ развернуть продакшн-среду без необходимости самостоятельно закупать и администрировать железо, концентрируясь на коде и бизнес-логике.

Заключение

Rust — язык, ориентированный на безопасное и эффективное системное программирование. Модель владения памятью, заимствования и проверки на этапе компиляции позволяет предотвратить целые классы ошибок до запуска программы.

Встроенные инструменты — компилятор, пакетный менеджер и система сборки Cargo — упрощают управление зависимостями, сборку, тестирование и публикацию приложений и библиотек.

В сочетании с гибкой инфраструктурой (например, в облаке Selectel) Rust становится удобным выбором для создания надёжных, высоконагруженных сервисов и системных компонентов.