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

Справочник по языку Rust


Справочник-шпаргалка по Rust — типы, синтаксис, стандартная библиотека, типовые паттерны. Не заменяет пошаговое обучение. Учебный курс: раздел.

Учебные статьи: Типы данных · Важные трейты и типы.

Любая программа на Rust состоит из одного или нескольких модулей, каждый из которых может содержать — объявления (use, mod), определения типов (struct, enum, union), функции (fn), константы (const, static), реализации (impl), макросы (macro_rules!, #[macro_export]). extern crate в edition 2021 почти не нужен — зависимости подключаются через Cargo.toml.


Справочные таблицы​

Содержание справочника​


Любая программа на Rust состоит из одного или нескольких модулей, каждый из которых может содержать:

  • объявления элементов (use, mod);
  • определения типов (struct, enum, union);
  • функции (fn);
  • константы (const, static);
  • реализации (impl);
  • макросы (macro_rules!, #[macro_export]).

Точка входа в исполняемую программу — функция main:

fn main() {
println!("Hello, world!");
}

Для библиотек точка входа не обязательна.


2. Комментарии​

Rust поддерживает три вида комментариев:

  • Однострочные: // это комментарий
  • Многострочные: /* это многострочный комментарий */
  • Документационные: /// это документация для следующего элемента

Документационные комментарии обрабатываются rustdoc и могут содержать Markdown.


3. Идентификаторы и ключевые слова​

Идентификаторы​

Идентификаторы в Rust:

  • начинаются с буквы или символа подчеркивания _;
  • могут содержать буквы, цифры, подчеркивания;
  • чувствительны к регистру;
  • не могут совпадать с зарезервированными ключевыми словами.

Примеры корректных идентификаторов:

  • counter
  • _temp
  • my_var_123
  • π (Unicode разрешён)

Зарезервированные ключевые слова​

Всегда зарезервированные​

as, break, const, continue, crate, else, enum, extern, false, fn, for, if, impl, in, let, loop, match, mod, move, mut, pub, ref, return, self, Self, static, struct, super, trait, true, type, unsafe, use, where, while


Зарезервированные для будущего использования​

abstract, become, box, do, final, macro, override, priv, typeof, unsized, virtual, yield

Эти слова нельзя использовать как идентификаторы без экранирования через обратные кавычки (raw identifiers): r#do.


4. Литералы​

Целочисленные литералы​

  • Десятичные: 42
  • Шестнадцатеричные: 0xFF
  • Восьмеричные: 0o77
  • Двоичные: 0b101010
  • Подчёркивания для читаемости: 1_000_000

Можно указывать суффиксы типа:

  • i8, i16, i32, i64, i128, isize
  • u8, u16, u32, u64, u128, usize

Пример: 42u32, 0xFF_i8


Числа с плавающей точкой​

  • Десятичные: 3.14, 2.0
  • Экспоненциальная форма: 1e10, 2.5E-3
  • Суффиксы: f32, f64

Пример: 3.1415_f64


Булевы литералы​

  • true
  • false

Символы​

  • Одинарные кавычки — 'a', 'α', '\n', '\u{1F600}'
  • Размер: всегда 4 байта (UTF-32)

Строковые литералы​

  • Двойные кавычки: "hello"
  • Экранирование — \n, \t, \", \\
  • Raw-строки: r#"..."#, r##"..."## (количество # должно совпадать)
  • Многострочные строки поддерживаются напрямую

Пример:

let s = "Line 1\nLine 2";
let raw = r#"C:\Users\Name"#;

Byte-литералы​

  • Байтовые строки: b"hello" → &[u8; 5]
  • Байтовые символы: b'A' → u8

Массивы и кортежи​

  • Массив — [1, 2, 3], [0; 10] (массив из 10 нулей)
  • Кортеж — (1, "hello", true)

5. Переменные и привязки​

Объявление переменной происходит через ключевое слово let:

let x = 5;
let mut y = 10;
y = 15; // разрешено только для mut

Особенности:

  • Переменные неизменяемы по умолчанию.
  • Изменяемость указывается явно через mut.
  • Привязки могут быть переопределены (shadowing):
let x = 5;
let x = x + 1; // новая привязка
let x = "hello"; // тип изменился — допустимо

Shadowing не требует mut и создаёт новую переменную с тем же именем.


6. Типы данных​

Скалярные типы​

Целые числа​
  • Со знаком — i8, i16, i32, i64, i128, isize
  • Без знака — u8, u16, u32, u64, u128, usize

isize и usize зависят от архитектуры (32 или 64 бита).


Числа с плавающей точкой​
  • f32, f64 (по умолчанию f64)

Булевый тип​
  • bool: значения true, false

Символы​
  • char — Unicode scalar value (например, 'A', '🦀', '\u{1F4A9}')

Составные типы​

Кортежи (tuples)​

Фиксированная длина, элементы разных типов:

let tup: (i32, f64, char) = (500, 6.4, 'x');
let (x, y, z) = tup; // деструктуризация
let second = tup.1; // доступ по индексу

Массивы​

Фиксированная длина, все элементы одного типа:

let arr: [i32; 5] = [1, 2, 3, 4, 5];
let zeros = [0; 10]; // массив из 10 нулей

Доступ по индексу: arr[0]. Выход за границы вызывает панику в режиме отладки.


Интерактивное демо — вызов функции и стек на примере JavaScript. В Rust объявление через fn, но вызов, локальные переменные и возврат устроены так же. Обобщённо: функции в коде.

Play ITЗагрузка интерактивного демо…


7. Функции​

Объявление функции:

fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}

Особенности:

  • Аргументы указываются с типами.
  • Возвращаемый тип указывается после стрелки ->.
  • Последнее выражение в блоке (без точки с запятой) является возвращаемым значением.
  • Явный return используется редко.

Функции могут быть:

  • const fn — вычисляемые во время компиляции;
  • unsafe fn — содержащие небезопасный код;
  • extern "C" fn — для FFI.

Пример const fn:

const fn square(x: i32) -> i32 {
x * x
}

8. Управляющие конструкции​

Условные выражения​

if​
if condition {
// ...
} else if other_condition {
// ...
} else {
// ...
}

if — выражение, возвращает значение:

let number = if condition { 5 } else { 6 };

Все ветви должны возвращать один и тот же тип.


match​

Полное выражение сопоставления с образцом:

match value {
1 => println!("one"),
2 | 3 => println!("two or three"),
4..=10 => println!("four to ten"),
_ => println!("something else"),
}

Особенности:

  • Обязательно покрытие всех вариантов (exhaustiveness).
  • _ — wildcard-образец.
  • Можно использовать диапазоны (..=, ..), охранники (if), деструктуризацию.

Циклы​

loop​

Бесконечный цикл:

let mut counter = 0;
loop {
counter += 1;
if counter == 10 {
break;
}
}

Может возвращать значение:

let result = loop {
break 42;
};

while​
while condition {
// ...
}

for​

Итерация по итератору:

for i in 0..5 {
println!("{}", i);
}

for item in vec.iter() {
println!("{}", item);
}

for автоматически берёт владение, заимствование или ссылку в зависимости от контекста.


9. Владение и заимствование (основы)​

Rust управляет памятью через систему владения:

  • Каждое значение имеет одного владельца.
  • Когда владелец выходит из области видимости, значение уничтожается.
  • Значение можно переместить (move) — передача владения.
  • Можно создать ссылку (&T) — заимствование.
  • Изменяемые ссылки (&mut T) позволяют изменять данные.
  • В любой момент времени допускается либо одна изменяемая ссылка, либо любое количество неизменяемых.

Пример:

let s1 = String::from("hello");
let s2 = s1; // s1 больше не действителен
// println!("{}", s1); // ошибка!

let s = String::from("world");
let r1 = &s;
let r2 = &s;
// let r3 = &mut s; // ошибка: нельзя одновременно иметь & и &mut

10. Структуры (struct)​

Структуры — это пользовательские составные типы данных. Rust поддерживает три вида структур.


10.1. Классические структуры с именованными полями​

struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}

Создание экземпляра:

let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};

Обновление через синтаксис обновления структуры:

let user2 = User {
email: String::from("another@example.com"),
..user1 // остальные поля скопированы из user1
};

После такого обновления user1.username становится недоступным, если String не реализует Copy.


10.2. Кортежные структуры (tuple structs)​

Поля не имеют имён, только позиции:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Типы Color и Point считаются разными, даже если содержат одинаковые данные.


10.3. Единичные структуры (unit-like structs)​

Структуры без полей:

struct AlwaysEqual;
let subject = AlwaysEqual;

Используются, когда нужен тип без данных, но с реализацией трейтов.


10.4. Методы для структур​

Методы определяются в блоке impl:

Код ITЗагрузка примера кода…

  • &self — заимствование экземпляра (неизменяемое).
  • &mut self — изменяемое заимствование.
  • self — передача владения (редко используется).

Вызов:

let mut user = User::new("a@b.com".to_string(), "alice".to_string());
user.activate();
println!("{}", user.greet());

11. Перечисления (enum)​

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


11.1. Базовое объявление​

enum IpAddrKind {
V4,
V6,
}

Использование:

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

11.2. Перечисления с данными​

Варианты могут содержать данные:

enum IpAddr {
V4(String),
V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

Можно использовать разные типы в разных вариантах:

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

11.3. Методы для перечислений​

Перечисления также могут иметь impl-блоки:

impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quitting..."),
_ => println!("Another message"),
}
}
}

11.4. Option<T> — стандартное перечисление​

enum Option<T> {
Some(T),
None,
}

Используется вместо отсутствия значения (как null в других языках). Обязательная обработка обоих случаев.

Пример:

fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}

11.5. Result<T, E> — обработка ошибок​

enum Result<T, E> {
Ok(T),
Err(E),
}

Используется для возврата успешного результата или ошибки.


12. Трейты (trait)​

Трейты определяют поведение, которое типы могут реализовать.


12.1. Определение трейта​

trait Summary {
fn summarize(&self) -> String;
}

12.2. Реализация трейта​

struct NewsArticle {
headline: String,
location: String,
}

impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, ({})", self.headline, self.location)
}
}

12.3. Правило сироты (orphan rule)​

Реализацию impl Trait for Type можно писать в крейте только если хотя бы один из Trait или Type определён в этом же крейте. Нельзя, например, добавить impl Display for Vec<T> в своём приложении — и Display, и Vec приходят извне.


12.4. Трейты с поведением по умолчанию​

trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}

Типы могут переопределять метод или использовать реализацию по умолчанию.


12.5. Расширение трейтов​

Трейт может требовать реализации другого трейта:

trait Displayable: Summary {
fn display(&self);
}

Любой тип, реализующий Displayable, обязан реализовать и Summary.


12.6. Супер-трейты​

Аналогично: trait A: B + C означает, что A требует B и C.


12.7. Ассоциированные типы​

trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}

Позволяют определять типы внутри трейта.


12.8. Обобщённые трейты и ограничения​

Функции могут принимать параметры с ограничениями по трейтам:

fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}

Или через where:

fn some_function<T, U>(t: T, u: U) -> i32
where
T: Summary + Clone,
U: Display,
{
// ...
}

13. Обобщения (generics)​

Общая теория — Обобщения и обобщённое программирование.

13.1. Обобщённые функции​

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}

13.2. Обобщённые структуры​

struct Point<T> {
x: T,
y: T,
}

let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };

Можно использовать несколько параметров:

struct Point<T, U> {
x: T,
y: U,
}

13.3. Обобщённые перечисления​

enum Result<T, E> {
Ok(T),
Err(E),
}

13.4. Обобщённые методы​

Методы могут иметь собственные параметры обобщения, независимо от структуры:

impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}

impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}

14. Жизненные циклы (lifetimes)​

Жизненные циклы — часть системы заимствования, гарантирующая, что ссылки всегда действительны.


14.1. Аннотации жизненных циклов​

Синтаксис — 'a, 'b, 'static

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}

Эта функция возвращает ссылку, которая живёт не дольше, чем обе входные ссылки.


14.2. Правила элиминации жизненных циклов​

Компилятор применяет три правила для автоматического вывода:

  1. Каждый параметр-ссылка получает собственный уникальный жизненный цикл.
  2. Если есть один входной параметр-ссылка, его жизненный цикл присваивается всем выходным ссылкам.
  3. Если среди параметров есть &self или &mut self, их жизненный цикл присваивается всем выходным ссылкам.

Если правила не позволяют вывести — требуется явная аннотация.


14.3. Статический жизненный цикл​

'static — самый длинный возможный жизненный цикл. Все строковые литералы имеют 'static:

let s: &'static str = "I have a static lifetime.";

14.4. Жизненные циклы в структурах​

Структуры, содержащие ссылки, должны указывать жизненные циклы:

struct ImportantExcerpt<'a> {
part: &'a str,
}

Реализация методов:

impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}

15. Смарт-поинтеры​

Смарт-поинтеры — это структуры данных, которые ведут себя как указатели, но предоставляют дополнительную функциональность (например, автоматическое управление памятью). В Rust смарт-поинтеры реализуют трейты Deref и Drop.


15.1. Box<T> — выделение на куче​

Используется для:

  • Хранения данных в куче.
  • Рекурсивных типов (например, деревьев).
  • Трейт-объектов (Box<dyn Trait>).

Пример:

let b = Box::new(5);
println!("b = {}", b); // 5

Рекурсивная структура:

enum List {
Cons(i32, Box<List>),
Nil,
}

15.2. Rc<T> — подсчёт ссылок (reference counting)​

Позволяет нескольким владельцам совместно использовать данные. Только для однопоточного использования.

use std::rc::Rc;

let a = Rc::new(String::from("hello"));
let b = Rc::clone(&a);
let c = Rc::clone(&a);

println!("count after creating c: {}", Rc::strong_count(&a)); // 3

Rc<T> не реализует Send или Sync — нельзя передавать между потоками.


15.3. Arc<T> — атомарный подсчёт ссылок​

Аналог Rc<T>, но потокобезопасный (Arc = Atomically Reference Counted).

use std::sync::Arc;
use std::thread;

let data = Arc::new(vec![1, 2, 3]);
let data1 = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("{:?}", data1);
});
handle.join().unwrap();

15.4. RefCell<T> — заимствование во время выполнения​

Обходит правила заимствования на этапе компиляции, проверяя их во время выполнения.

  • Позволяет иметь изменяемое заимствование даже при неизменяемой ссылке.
  • Паникует при нарушении правил (например, одновременное изменяемое и неизменяемое заимствование).
use std::cell::RefCell;

let x = RefCell::new(5);
{
let mut m = x.borrow_mut();
*m += 1;
}
println!("{}", x.borrow()); // 6

15.5. Cell<T> — внутренняя изменяемость без заимствования​

Поддерживает только типы, реализующие Copy. Не возвращает ссылки — работает через get/set.

use std::cell::Cell;

let c = Cell::new(10);
c.set(20);
println!("{}", c.get()); // 20

15.6. Комбинирование смарт-поинтеров​

Частые комбинации:

  • Rc<RefCell<T>> — разделяемая изменяемость в одном потоке.
  • Arc<Mutex<T>> — разделяемая изменяемость между потоками.
  • Box<dyn Trait> — динамическая диспетчеризация.

16. Обработка ошибок​

Rust не использует исключения. Ошибки обрабатываются через типы.


16.1. panic!​

Вызывает немедленную остановку программы (в режиме отладки — с трассировкой стека).

panic!("Something went wrong!");

Используется при невозможности восстановления (например, логические ошибки).


16.2. Result<T, E>​

Основной способ обработки восстанавливаемых ошибок.

fn read_file(path: &str) -> Result<String, std::io::Error> {
std::fs::read_to_string(path)
}

16.3. Оператор ?​

Сокращает обработку Result:

fn main() -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string("file.txt")?;
println!("{}", content);
Ok(())
}

Оператор ?:

  • Возвращает значение, если Ok.
  • Возвращает Err из функции, если Err.

Можно использовать только в функциях, возвращающих Result или Option.


16.4. Преобразование ошибок​

Используйте map_err, and_then, or_else:

let result = read_file("data.txt")
.map_err(|e| format!("Failed to read file: {}", e))?;

Для сложных случаев — создание собственного типа ошибки или использование библиотек (thiserror, anyhow).


16.5. Option<T> и безопасность​

Вместо "пустого" указателя — Option::Some(value) или Option::None.

Безопасные методы:

  • unwrap_or(default)
  • unwrap_or_else(|| compute())
  • map, and_then, filter, ok_or

17. Макросы​

Макросы — это метапрограммирование на этапе компиляции.


17.1. Декларативные макросы (macro_rules!)​

Похожи на шаблоны сопоставления.

macro_rules! say_hello {
() => {
println!("Hello!");
};
($name:expr) => {
println!("Hello, {}!", $name);
};
}

say_hello!(); // Hello!
say_hello!("Alice"); // Hello, Alice!

Шаблоны могут содержать повторения:

macro_rules! vec_sum {
($($x:expr),*) => {
0 $(+ $x)*
};
}
let s = vec_sum!(1, 2, 3); // 6

17.2. Процедурные макросы​

Работают как функции, принимающие токены и возвращающие токены. Три типа:


17.2.1. Макросы-функции (#[proc_macro])​

Используются как функции:

use proc_macro::TokenStream;

#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
"fn answer() -> u32 { 42 }".parse().unwrap()
}

Вызов:

make_answer!();

17.2.2. Макросы-атрибуты (#[proc_macro_attribute])​

Применяются к элементам:

#[my_macro]
fn my_function() { }

17.2.3. Макросы-производные (#[proc_macro_derive])​

Автоматически генерируют код для #[derive(...)]:

#[derive(MyTrait)]
struct MyStruct;

17.3. Встроенные макросы​

  • println!, eprintln!, format! — форматированный вывод.
  • vec! — создание вектора — vec![1, 2, 3], vec![0; 10].
  • assert!, assert_eq!, debug_assert! — проверки.
  • todo!, unimplemented! — заглушки.
  • include_str!, include_bytes! — встраивание файлов во время компиляции.

18. Модули и организация кода​

18.1. Ключевые слова​

  • mod — объявление модуля.
  • use — импорт путей.
  • pub — экспорт элементов.
  • crate — корень текущего крейта.
  • super — родительский модуль.

18.2. Иерархия модулей​

Файл main.rs:

mod network {
pub mod server {
pub fn connect() {}
}
}

Или через отдельные файлы:

src/
├── main.rs
└── network/
├── mod.rs
└── server.rs

mod.rs определяет содержимое модуля network.


18.3. Пути​

  • Абсолютные: crate::network::server::connect
  • Относительные: super::utils::helper

18.4. Экспорт​

По умолчанию всё приватно. Для экспорта:

pub mod server;
pub fn connect() {}
pub struct Config;

19. Cargo — система сборки и управления зависимостями​

19.1. Файл Cargo.toml​

Основной манифест проекта. Минимальная структура:

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1.0"
tokio = { version = "1.0", features = ["full"] }

19.2. Секции манифеста​

[package]​

Обязательные поля:

  • name — имя крейта (должно быть валидным идентификатором Rust)
  • version — семантическое версионирование (MAJOR.MINOR.PATCH)
  • edition — версия языка (2015, 2018, 2021)

Опциональные:

  • authors, description, license, repository, readme, keywords, categories

[dependencies]​

Зависимости времени выполнения:

[dependencies]
rand = "0.8" # любая совместимая версия
regex = "~0.3.5" # патч-версии (~0.3.x)
lazy_static = "=1.4.0" # строго эта версия

[dev-dependencies]​

Зависимости только для тестов и разработки:

[dev-dependencies]
criterion = "0.5"

[build-dependencies]​

Зависимости для скриптов сборки (build.rs).


[features]​

Условная компиляция:

[features]
default = ["native-tls"]
native-tls = ["tokio/native-tls"]
rustls = ["tokio/rustls"]

Включение фичи:

cargo build --features "rustls"

[workspace]​

Объединяет несколько крейтов:

[workspace]
members = ["crates/*", "examples/my_tool"]

Каждый член workspace должен иметь свой Cargo.toml.


19.3. Профили сборки​

Определяют параметры компиляции:

[profile.dev]
opt-level = 0 # отладка
debug = true
overflow-checks = true

[profile.release]
opt-level = 3 # максимальная оптимизация
debug = false
lto = true # link-time optimization
codegen-units = 1 # медленнее, но лучше оптимизация
strip = true # удалять символы отладки

Доступные профили — dev, release, test, bench, doc.


19.4. Команды Cargo​

КомандаНазначение
cargo buildСборка в режиме разработки
cargo build --releaseСборка с оптимизациями
cargo runСборка и запуск
cargo testЗапуск тестов
cargo checkБыстрая проверка без генерации кода
cargo docГенерация документации
cargo publishПубликация на crates.io
cargo installУстановка бинарного крейта
cargo updateОбновление зависимостей

19.5. Скрипты сборки (build.rs)​

Выполняются перед компиляцией. Используются для:

  • Генерации кода
  • Проверки системных зависимостей
  • Настройки окружения

Пример:

// build.rs
use std::env;
use std::fs::File;
use std::io::Write;

fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = std::path::Path::new(&out_dir).join("version.rs");
let mut f = File::create(&dest_path).unwrap();
f.write_all(b"pub const VERSION: &str = \"1.0.0\";").unwrap();
}

20. Тестирование​

20.1. Модульные тесты (unit tests)​

Размещаются внутри файла с тестируемым кодом:

Код ITЗагрузка примера кода…

Запуск:

cargo test
cargo test it_works
cargo test -- --ignored

20.2. Интеграционные тесты​

Размещаются в директории tests/:

src/
tests/
├── integration_test.rs
└── db/
└── mod.rs

Каждый файл — отдельный крейт. Доступ к публичному API основного крейта.

// tests/integration_test.rs
use my_project::add;

#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}

20.3. Документационные тесты​

Примеры в /// комментариях автоматически тестируются:

/// Adds two numbers.
///
/// # Примеры
///
/// ```
/// let result = my_crate::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

Запуск: cargo test --doc


20.4. Бенчмарки​

Требуют nightly или criterion:

#[cfg(test)]
mod benches {
use super::*;
use test::Bencher;

#[bench]
fn bench_add(b: &mut Bencher) {
b.iter(|| add(2, 3));
}
}

Или через criterion (стабильный):

[dev-dependencies]
criterion = "0.5"

[[bench]]
name = "my_benchmark"
harness = false

21. Атрибуты​

Атрибуты — метаданные, влияющие на компиляцию.


21.1. Встроенные атрибуты​

Условная компиляция​
#[cfg(target_os = "linux")]
fn platform_specific() {}

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

Оптимизация​
#[inline] // рекомендовать инлайнинг
#[inline(always)] // принудительный инлайнинг
#[inline(never)] // запрет инлайнинга

Представление типов​
#[repr(C)] // совместимость с C
#[repr(u8)] // для enum — явный дискриминант
#[repr(packed)] // плотная упаковка (осторожно!)

Документация​
#[doc(hidden)] // скрыть из документации
#[doc(alias = "old_name")] // альтернативное имя поиска

Безопасность​
#[unsafe(no_sanitize = "address")] // отключить ASan

21.2. Производные атрибуты​

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Point {
x: i32,
y: i32,
}

Стандартные трейты для derive:

  • Clone, Copy
  • Debug, Default
  • PartialEq, Eq
  • PartialOrd, Ord
  • Hash
  • Serialize, Deserialize (из serde)

22. FFI — взаимодействие с C​

Практический разбор (build.rs, bindgen, safe-обёртки): FFI на практике.


22.1. Вызов Rust из C​

Экспорт функции:

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}

Флаги в Cargo.toml:

[lib]
crate-type = ["cdylib"] # для .so/.dll/.dylib

22.2. Вызов C из Rust​

Объявление внешней функции:

extern "C" {
fn abs(input: i32) -> i32;
}

fn main() {
unsafe {
println!("abs(-5) = {}", abs(-5));
}
}

Использование bindgen для генерации Rust-обвязок из заголовков C.


22.3. Типы данных в FFI​

Безопасные типы:

  • i8, u32, f64 → int8_t, uint32_t, double
  • *const T, *mut T → указатели
  • c_char, c_int из std::os::raw

Строки:

  • Передача в C: CString::new("hello").unwrap().as_ptr()
  • Приём из C: CStr::from_ptr(ptr).to_str().unwrap()

23. Асинхронное программирование​

23.1. Основы async/await​

Асинхронные функции возвращают Future:

async fn fetch_data() -> String {
"data".to_string()
}

Вызов:

#[tokio::main]
async fn main() {
let data = fetch_data().await;
println!("{}", data);
}

Ключевые моменты:

  • async fn не блокирует поток.
  • .await приостанавливает выполнение до завершения Future.
  • Код внутри async fn может быть прерван в точках .await.

23.2. Тип Future​

Трейт Future определяет асинхронную операцию:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

struct MyFuture;

impl Future for MyFuture {
type Output = i32;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(42)
}
}

В реальном коде редко реализуется вручную — генерируется компилятором для async fn.


23.3. Рантаймы​

Rust не предоставляет встроенный асинхронный рантайм. Популярные варианты:

  • Tokio — промышленный, поддерживает I/O, таймеры, задачи.
  • async-std — API, похожий на std, но асинхронный.
  • smol — минималистичный рантайм.

Пример с Tokio (Cargo.toml):

[dependencies]
tokio = { version = "1.0", features = ["full"] }

23.4. Асинхронные задачи​

Запуск задач в фоне:

tokio::spawn(async {
println!("Background task");
});

Ожидание результата:

let handle = tokio::spawn(async { 42 });
let result = handle.await.unwrap(); // 42

23.5. Асинхронные каналы​

Из tokio::sync::mpsc:

use tokio::sync::mpsc;

#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(32);
tokio::spawn(async move {
tx.send("hello").await.unwrap();
});
let msg = rx.recv().await.unwrap();
println!("{}", msg);
}

23.6. Асинхронные блокировки​

  • tokio::sync::Mutex — асинхронная мьютекс-блокировка.
  • Не блокирует поток, а уступает управление.
use tokio::sync::Mutex;
let data = Arc::new(Mutex::new(0));

24. Многопоточность​

24.1. Создание потоков​

use std::thread;

let handle = thread::spawn(|| {
"Hello from thread"
});

let result = handle.join().unwrap();
println!("{}", result);

24.2. Совместный доступ к данным​

Arc<T> — атомарный подсчёт ссылок​

Позволяет делить владение между потоками.


Mutex<T> — взаимоисключающая блокировка​

Код ITЗагрузка примера кода…


24.3. Каналы (std::sync::mpsc)​

Межпоточная передача сообщений:

use std::sync::mpsc;
use std::thread;

let (tx, rx) = mpsc::channel();

thread::spawn(move || {
tx.send("message").unwrap();
});

let received = rx.recv().unwrap();
println!("{}", received);

Типы каналов:

  • mpsc::channel() — один отправитель, один получатель.
  • mpsc::Sender::clone() — несколько отправителей.

24.4. Безопасность между потоками​

Трейты:

  • Send — тип можно передавать между потоками.
  • Sync — ссылку на тип можно использовать из нескольких потоков.

Большинство стандартных типов реализуют Send и Sync. Исключения:

  • Rc<T> — не Send, не Sync
  • RefCell<T> — не Send, не Sync
  • Mutex<T>, Arc<T> — Send и Sync (если содержимое тоже)

25. Небезопасный код (unsafe)​

25.1. Когда разрешён unsafe​

Пять операций, требующих unsafe:

  1. Разыменование сырого указателя.
  2. Вызов небезопасной функции или метода.
  3. Реализация небезопасного трейта.
  4. Чтение или запись в статическую переменную mut.
  5. Доступ к полям union.

25.2. Сырые указатели​

let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

unsafe {
println!("r1: {}", *r1);
*r2 = 10;
}

Сырые указатели не проверяются на валидность.


25.3. Небезопасные функции​

unsafe fn dangerous_function() {
// ...
}

fn main() {
unsafe {
dangerous_function();
}
}

Автор обязан гарантировать соблюдение инвариантов.


25.4. Небезопасные трейты​

unsafe trait DangerousTrait { }

unsafe impl DangerousTrait for MyType { }

Используется редко (например, Send, Sync для специальных типов).


26. Расширенные паттерны сопоставления​

26.1. Деструктуризация​

Структуры​
struct Point { x: i32, y: i32 }
let p = Point { x: 0, y: 7 };
let Point { x: a, y: b } = p;
// или
let Point { x, y } = p; // если имена совпадают

Кортежи и перечисления​
enum Message {
Quit,
Move { x: i32, y: i32 },
}

let msg = Message::Move { x: 1, y: 2 };
match msg {
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
_ => (),
}

26.2. Охранники (if в match)​

match value {
n if n < 0 => println!("Negative"),
n if n > 0 => println!("Positive"),
_ => println!("Zero"),
}

26.3. Диапазоны​

match age {
0..=12 => println!("Child"),
13..=19 => println!("Teen"),
_ => println!("Adult"),
}

Поддерживаются:

  • .. — исключает правую границу
  • ..= — включает правую границу

26.4. Игнорирование значений​

  • _ — игнорировать значение
  • .. — игнорировать остаток структуры/кортежа
let numbers = (1, 2, 3, 4, 5);
let (first, .., last) = numbers;

27. Стандартная библиотека — обзор ключевых модулей​

27.1. std::collections​

  • Vec<T> — динамический массив
  • HashMap<K, V> — хеш-таблица
  • HashSet<T> — множество
  • LinkedList<T>, VecDeque<T> — двусвязный список и дек
  • BinaryHeap<T> — макс-куча

27.2. std::fs — работа с файловой системой​

use std::fs;

let contents = fs::read_to_string("file.txt")?;
fs::write("output.txt", "Данные")?;

27.3. std::net — сетевое взаимодействие​

TCP-сервер:

use std::net::TcpListener;

let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
for stream in listener.incoming() {
// обработка соединения
}

27.4. std::time — время и задержки​

use std::time::{Duration, Instant};

let now = Instant::now();
thread::sleep(Duration::from_secs(1));
println!("Elapsed: {:?}", now.elapsed());

27.5. std::env — окружение​

let args: Vec<String> = env::args().collect();
let home = env::var("HOME").unwrap();

27.6. std::process — управление процессами​

use std::process::Command;

let output = Command::new("ls").output().unwrap();
println!("{}", String::from_utf8_lossy(&output.stdout));

28. Производительность и профилирование​

28.1. Оптимизация на уровне компилятора​

Rust использует LLVM в качестве бэкенда. Уровни оптимизации:

  • opt-level = 0 — отладка, без оптимизаций
  • opt-level = 1..3 — увеличение агрессивности оптимизаций
  • opt-level = "s" — минимизация размера
  • opt-level = "z" — максимальное сжатие (как "s", но с дополнительными мерами)

Рекомендации:

  • Используйте --release для измерения производительности.
  • Включайте lto = true и codegen-units = 1 в релизных сборках.
  • Избегайте избыточного копирования: предпочитайте &str вместо String при чтении.

28.2. Профилирование​

Инструменты:

  • perf (Linux) — системный профайлер
  • Instruments (macOS) — встроенный профайлер
  • VTune, Callgrind, FlameGraph — для детального анализа

Пример с FlameGraph:

cargo build --release
perf record target/release/my_app
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg

28.3. Измерение времени выполнения​

Для микро-бенчмарков — используйте criterion:

use criterion::{criterion_group, criterion_main, Criterion};

fn benchmark_add(c: &mut Criterion) {
c.bench_function("add", |b| b.iter(|| add(2, 3)));
}

criterion_group!(benches, benchmark_add);
criterion_main!(benches);

Запуск: cargo bench


28.4. Аллокации и память​

  • Минимизируйте количество выделений в куче.
  • Используйте Vec::with_capacity() при известном размере.
  • Рассмотрите SmallVec, ArrayVec для малых коллекций (из crates.io).
  • Избегайте .clone() без необходимости — передавайте ссылки.

29. Обработка ошибок на продакшене​

29.1. Иерархия ошибок​

Создайте собственный тип ошибки:

#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
Custom(String),
}

Или используйте библиотеки:

  • thiserror — для библиотек (реализует std::error::Error)
  • anyhow — для приложений (гибкая обёртка над ошибками)

Пример с thiserror:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConfigError {
#[error("Invalid port: {0}")]
InvalidPort(#[from] std::num::ParseIntError),
#[error("Missing config file")]
MissingFile,
}

29.2. Логирование​

Используйте tracing или log + env_logger:

use tracing::{info, error};

#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
info!("Application started");
error!("Something went wrong");
}

29.3. Отказоустойчивость​

  • Оборачивайте задачи в tokio::spawn с обработкой паник.
  • Используйте Result вместо panic! в публичном API.
  • Предусматривайте тайм-ауты для внешних вызовов.

30. Лучшие практики и идиомы Rust​

30.1. Владение и заимствование​

  • Передавайте владение только тогда, когда значение больше не нужно у вызывающего.
  • Используйте &T для чтения, &mut T для изменения.
  • Избегайте Rc<RefCell<T>> в новых проектах — предпочитайте архитектуру без разделяемого состояния.

30.2. API-дизайн​

  • Возвращайте Result для операций, которые могут завершиться неудачей.
  • Используйте Into<T> и AsRef<T> для гибкости входных параметров:
fn process_path<P: AsRef<Path>>(path: P) { ... }
  • Предоставляйте конструкторы (new, from_*) и билдеры для сложных типов.

30.3. Типажи и обобщения​

  • Ограничивайте обобщения минимально необходимыми трейтами.
  • Используйте impl Trait в возвращаемых типах для упрощения сигнатур.
  • Избегайте избыточной обобщённости — конкретные типы упрощают понимание.

30.4. Безопасность​

  • Минимизируйте использование unsafe.
  • Если unsafe необходим — изолируйте его в небольшой модуль с чёткими инвариантами.
  • Документируйте все предусловия для unsafe блоков.

31. Инструменты экосистемы​

31.1. rustfmt​

Автоматическое форматирование кода. Конфигурация через rustfmt.toml.

Запуск:

cargo fmt

31.2. clippy​

Статический анализатор, находящий антипаттерны.

Запуск:

cargo clippy
cargo clippy -- -D warnings # treat as errors

Популярные линты:

  • unwrap_used — рекомендует заменить unwrap() на безопасную обработку
  • missing_docs — требует документацию
  • too_many_arguments — сигнализирует о проблемах проектирования

31.3. miri​

Интерпретатор MIR для обнаружения неопределённого поведения (UB).

Запуск:

cargo +nightly miri test

Находит:

  • Использование неинициализированной памяти
  • Нарушения правил aliasing
  • Арифметику с переполнением (в debug)

31.4. cargo-expand​

Показывает раскрытый код макросов.

Установка:

cargo install cargo-expand

Использование:

cargo expand my_macro

31.5. cargo-deny​

Проверяет лицензии, зависимости, дублирование.


32. Отладка и диагностика​

32.1. Отладочные сообщения​

dbg!(value); // печатает файл, строку и значение

32.2. Отладка в IDE​

  • Rust Analyzer — основной LSP-сервер (поддержка в VS Code, JetBrains, Vim)
  • Поддержка перехода к определению, рефакторинга, подсказок типов

32.3. Анализ паник​

Используйте RUST_BACKTRACE=1:

RUST_BACKTRACE=1 cargo run

Варианты:

  • RUST_BACKTRACE=short — краткий трейс
  • RUST_BACKTRACE=full — полный трейс

32.4. Проверка утечек памяти​

  • Rust не имеет сборщика мусора, но утечки возможны через циклические ссылки (Rc/Arc).
  • Используйте Weak<T> для разрыва циклов.
  • Проверяйте счётчики ссылок в тестах.

В подборках​

Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:

Справочники — Справочник по языку Groovy, Справочник по языку Swift, Справочник по языку Ruby, Справочник по Lua, Справочник по языку Go, Справочник по COBOL.