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

5.13. Справочник по Rust

Разработчику Архитектору

Справочник по Rust

1. Структура программы

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

  • объявления элементов (use, mod, extern crate);
  • определения типов (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]. Выход за границы вызывает панику в режиме отладки.


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:

impl User {
fn new(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}

fn greet(&self) -> String {
format!("Hello, {}!", self.username)
}

fn activate(&mut self) {
self.active = true;
}
}
  • &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. Требования по реализации

  • Трейт и тип должны быть определены в текущем крейте (правило орфана), либо:
    • Трейт определён локально, и тип внешний → можно реализовать.
    • Тип определён локально, и трейт внешний → можно реализовать.
    • Оба внешние → нельзя реализовать.

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> и безопасность

Вместо nullOption::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)

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

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

#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}

#[test]
#[should_panic]
fn this_panics() {
panic!("Oops!");
}

#[test]
#[ignore]
fn expensive_test() {
// пропускается при обычном `cargo test`
}
}

Запуск:

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.
///
/// # Examples
///
/// ```
/// 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

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, f64int8_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> — взаимоисключающая блокировка

use std::sync::{Arc, Mutex};
use std::thread;

let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}

println!("Result: {}", *counter.lock().unwrap());

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", "data")?;

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> для разрыва циклов.
  • Проверяйте счётчики ссылок в тестах.