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_tempmy_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,isizeu8,u16,u32,u64,u128,usize
Пример: 42u32, 0xFF_i8
Числа с плавающей точкой
- Десятичные:
3.14,2.0 - Экспоненциальная форма:
1e10,2.5E-3 - Суффиксы:
f32,f64
Пример: 3.1415_f64
Булевы литералы
truefalse
Символы
- Одинарные кавычки:
'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. Правила элиминации жизненных циклов
Компилятор применяет три правила для автоматического вывода:
- Каждый параметр-ссылка получает собственный уникальный жизненный цикл.
- Если есть один входной параметр-ссылка, его жизненный цикл присваивается всем выходным ссылкам.
- Если среди параметров есть
&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> и безопасность
Вместо null — 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)
Размещаются внутри файла с тестируемым кодом:
#[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,CopyDebug,DefaultPartialEq,EqPartialOrd,OrdHashSerialize,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,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> — взаимоисключающая блокировка
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, неSyncRefCell<T>— неSend, неSyncMutex<T>,Arc<T>—SendиSync(если содержимое тоже)
25. Небезопасный код (unsafe)
25.1. Когда разрешён unsafe
Пять операций, требующих unsafe:
- Разыменование сырого указателя.
- Вызов небезопасной функции или метода.
- Реализация небезопасного трейта.
- Чтение или запись в статическую переменную
mut. - Доступ к полям
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>для разрыва циклов. - Проверяйте счётчики ссылок в тестах.