метапрограммирование на уровне ASTAST-level metaprogramming
macro_rules! и derive-макросы работают с синтаксическим деревом на этапе компиляции, до типовой проверки — в отличие от текстовой подстановки препроцессора C. vec! и println! — это обычные macro_rules!, а не встроенная магия компилятора.macro_rules! and derive macros operate on the syntax tree at compile time, before type-checking — unlike C's textual preprocessor substitution. vec! and println! are ordinary macro_rules! macros, not compiler built-ins.
У Rust две основные системы макросов, обе работающие на СИНТАКСИЧЕСКОМ ДЕРЕВЕ (в отличие от текстовых, token-substitution макросов препроцессора C) на этапе компиляции, до обычной типовой проверки: декларативные макросы (macro_rules!), определяемые через сопоставление с образцом на входных токен-деревьях, подставляемых в шаблон — например, vec![1, 2, 3] и println!("{}", x) стандартной библиотеки сами по себе являются обычными macro_rules! макросами, а не встроенными в компилятор конструкциями; и процедурные макросы, самая распространённая разновидность которых — derive-макрос (#[derive(Debug, Clone, PartialEq)]) — функция, выполняемая на этапе компиляции, получающая синтаксическое дерево аннотированной структуры/enum'а на входе и генерирующая дополнительный Rust-код (обычно реализацию трейта), который компилируется вместе с исходным определением.Rust has two main macro systems, both operating on the SYNTAX TREE (unlike C's textual, token-substitution preprocessor macros) at compile time, before normal type-checking: declarative macros (macro_rules!), defined via pattern-matching on token trees fed into a template — e.g. the standard library's own vec![1, 2, 3] and println!("{}", x) are themselves ordinary macro_rules! macros, not compiler built-ins; and procedural macros, of which the most common variety by far is a derive macro (#[derive(Debug, Clone, PartialEq)]) — a function that runs at compile time, receives the annotated struct/enum's syntax tree as input, and generates additional Rust code (typically a trait implementation) that gets compiled alongside the original definition.
macro_rules! решает задачу написания кода, действительно повторяющегося на СИНТАКСИЧЕСКОМ уровне, но невыразимого обычной обобщённой функцией, потому что нужно принимать переменное число аргументов разнородных типов со специальным синтаксисом (как скобочно-запятой синтаксис vec![1, 2, 3]) — обычная функция фундаментально не может иметь переменной длины, синтаксически особый список параметров так, как это может макрос, поскольку макрос работает до/вне обычной типизированной машинерии вызова функций.macro_rules! solves the problem of writing code that's genuinely repetitive at the syntactic level but can't be expressed as an ordinary generic function because it needs to accept a variable number of arguments of heterogeneous types with special syntax (like vec!'s bracket-and-comma syntax) — a generic function fundamentally cannot have a variable-length, syntactically-special parameter list the way a macro can, since a macro operates before/outside the normal type-checked function-call machinery.
Derive-макросы решают другую, чрезвычайно распространённую задачу: реализация простых, механических, чисто структурных реализаций трейтов (Debug печатает каждое поле, Clone клонирует каждое поле, PartialEq сравнивает каждое поле) вручную для каждой структуры/enum'а в кодовой базе была бы огромным, подверженным ошибкам boilerplate'ом.Derive macros solve a different, extremely common problem: implementing simple, mechanical, purely-structural trait implementations (Debug printing every field, Clone cloning every field, PartialEq comparing every field) by hand for every struct/enum in a codebase would be enormous, error-prone boilerplate.
#[derive(...)] генерирует такой код автоматически и корректно — концептуально похоже на (и значительно более общо, чем) компилятором генерируемые специальные функции-члены в C++ (конструктор по умолчанию, операторы сравнения через = default), только применимо к куда более широкому набору трейтов.#[derive(...)] generates this mechanical code automatically and correctly — conceptually similar to (and considerably more general than) C++'s compiler-generated default constructors/comparison operators via = default, just applicable to a much broader set of traits.
Упрощённое определение macro_rules! сопоставляет собственный синтаксис вызова макроса с образцом:A simplified macro_rules! definition matches the macro's own call syntax against a pattern:
macro_rules! max {
($a:expr, $b:expr) => {
if $a > $b { $a } else { $b }
};
}
let m = max!(3, 7); // раскрывается в if 3 > 7 { 3 } else { 7 }// expands to if 3 > 7 { 3 } else { 7 }
#[derive(Debug, Clone, PartialEq)] на простой структуре концептуально раскрывается в написанные вручную реализации — impl Debug for Point, делающую ровно ту же пофлдовую работу:#[derive(Debug, Clone, PartialEq)] on a simple struct conceptually expands to hand-written implementations — an impl Debug for Point doing the equivalent field-by-field work:
#[derive(Debug, Clone, PartialEq)]
struct Point { x: i32, y: i32 }
// derive(Debug) концептуально генерирует что-то вроде:// derive(Debug) conceptually generates something like:
impl std::fmt::Debug for Point {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Point").field("x", &self.x).field("y", &self.y).finish()
}
}
Помимо derive, существуют и полноценные процедурные макросы — attribute-подобные (#[my_macro]) и function-подобные (my_macro!(...), определяемые как настоящие Rust-функции, манипулирующие потоками токенов, а не паттернами macro_rules!) — более продвинутая тема, требующая отдельного крейта; здесь она лишь упоминается для полноты, основной фокус этой страницы — macro_rules! и derive.Beyond derive, fully custom procedural macros also exist — attribute-like (#[my_macro]) and function-like (my_macro!(...), defined as actual Rust functions manipulating token streams, not macro_rules! patterns) — a more advanced topic requiring a separate crate; it's only name-dropped here for completeness, this page's main focus is macro_rules! and derive.
Оба — реальные macro_rules! макросы стандартной библиотеки; вариадический синтаксис vec! с квадратными скобками концептуально определяется через паттерн повторения.Both are real standard-library macro_rules! macros; vec!'s variadic bracket syntax is conceptually defined via a repetition pattern.
macro_rules! my_vec {
($($x:expr),*) => {
{
let mut v = Vec::new();
$(v.push($x);)*
v
}
};
}
// my_vec![1, 2, 3] раскрывается в последовательность push()// my_vec![1, 2, 3] expands into a sequence of push() calls
derive(Debug) для быстрой диагностической печати во время разработки (println!("{:?}", my_struct)) экономит именно тот boilerplate, который пришлось бы писать вручную для каждого поля.derive(Debug) for quick diagnostic printing during development (println!("{:?}", my_struct)) saves exactly the boilerplate that would otherwise need to be hand-written for every field.
#[derive(Debug)]
struct Request { url: String, timeout_ms: u32 }
let r = Request { url: String::from("http://x"), timeout_ms: 500 };
println!("{:?}", r); // Request { url: "http://x", timeout_ms: 500 } — без единой строчки вручную// Request { url: "http://x", timeout_ms: 500 } — with zero lines written by hand
Позволяет использовать пользовательскую структуру как ключ HashMap без единой строчки ручной реализации трейтов — опирается на trait bound машинерию, описанную в generics.html.Enables a custom struct to be used as a HashMap key with zero manual trait-implementation code — relying on the trait-bound machinery described in generics.html.
#[derive(PartialEq, Eq, Hash)]
struct UserId(u64);
let mut map: HashMap<UserId, String> = HashMap::new();
map.insert(UserId(1), String::from("alice")); // требует Eq + Hash, оба получены через derive// requires Eq + Hash, both obtained via derive
Макрос hashmap!{ "a" => 1, "b" => 2 }, имитирующий синтаксис map-литерала, которого нет в самом языке, — показывает паттерн повторения $($key:expr => $value:expr),* и его раскрытие.A hashmap!{ "a" => 1, "b" => 2 } macro mimicking map-literal syntax the language itself doesn't have built in — showing the repetition pattern $($key:expr => $value:expr),* and its expansion.
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {
{
let mut m = HashMap::new();
$(m.insert($key, $value);)*
m
}
};
}
let m = hashmap!{ "a" => 1, "b" => 2 };
macro_rules! сопоставляются через сопоставление с образцом на токен-деревьях, а не через текстовую подстановку общего назначения — это делает их значительно безопаснее макросов препроцессора C (аргументы захватываются целиком как синтаксические фрагменты вроде $x:expr, а не вставляются как сырой текст, поэтому нет классических багов вроде #define SQUARE(x) x*x, ломающегося на SQUARE(a+b)), но написание действительно сложных macro_rules! (с множеством форм вызова, рекурсивным раскрытием) может стать собственным неуклюжим подъязыком со своими, часто менее ясными, чем у обычного Rust, сообщениями об ошибках.macro_rules! macros are matched via pattern matching on token trees, not a general-purpose text substitution — this makes them considerably safer than C preprocessor macros (arguments are captured as whole syntax fragments like $x:expr, not spliced in as raw text, so there's no classic bug like #define SQUARE(x) x*x breaking on SQUARE(a+b)) but also means writing genuinely complex macro_rules! macros can become its own small, awkward sub-language, with error messages that are often less clear than ordinary Rust compile errors.
Derive-макросы генерируют код только по СТРУКТУРНОЙ форме типа (имена полей, типы, варианты enum'а) — они не могут выразить произвольную кастомную логику по полю, как это может ручная реализация трейта (например, #[derive(Debug)] всегда печатает все поля в фиксированном механическом формате; чтобы скрыть одно поле из Debug-вывода, нужно писать impl Debug вручную или использовать атрибут уровня поля, если конкретный derive-макрос его поддерживает, как #[serde(skip)] — но это уже тема ecosystem/, не самого языка).Derive macros can only generate code based on the structural shape of the type they're attached to (field names, types, and enum variants) — they cannot express arbitrary custom logic per-field the way a hand-written trait implementation could (e.g. #[derive(Debug)] will always print every field in a fixed, mechanical format; to hide one field from Debug output you need to hand-write impl Debug yourself, or reach for a field-level attribute if the specific derive macro supports one, like serde's #[serde(skip)] — but that's an ecosystem/ topic, not the language itself).
Раскрытие макроса происходит полностью до типовой проверки, а значит макрос может сгенерировать синтаксически корректный, но приводящий к запутанному сообщению об ошибке типов код, указывающий на РАСКРЫТЫЙ код (который пользователь макроса никогда напрямую не писал), а не ясно на место вызова макроса — отладка таких ошибок типов, особенно для сторонних процедурных макросов, чьих внутренностей читатель не видел, — заметно более сложная категория Rust-ошибок, чем обычно.Macro expansion happens fully before type-checking, meaning a macro can generate code that's syntactically well-formed but produces a confusing type-error message that points at the expanded code (which the macro's user never wrote directly) rather than clearly at the macro invocation site — debugging macro-related type errors is a genuinely harder-than-average category of Rust error message to interpret, especially for third-party procedural macros the reader hasn't seen the internals of.