# Макросы Rust

> Механизм метапрограммирования Rust, генерирующий код на этапе компиляции; декларативные макросы macro_rules! и процедурные макросы — derive, атрибутные и функционально-подобные.

Источник: https://llmdocs.ru/wiki/rust-macros/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Язык**: Rust
- **Виды**: декларативные (macro_rules!), процедурные (derive, атрибутные, function-like)
- **Работают с**: потоком токенов, а не текстом
- **Гигиена**: локальные имена макроса не конфликтуют с кодом вызова
- **Популярные крейты**: syn, quote, proc-macro2
- **Просмотр раскрытия**: cargo expand
**Макросы Rust** — механизм метапрограммирования, который генерирует код во время компиляции. В отличие от макросов препроцессора C, работающих с текстом, макросы [Rust](https://llmdocs.ru/wiki/rust.md) оперируют синтаксическими токенами, проверяются компилятором и соблюдают **гигиену** — имена, объявленные внутри макроса, не сталкиваются с именами в месте вызова. Макрос узнаётся по восклицательному знаку (`println!`, `vec!`) или по атрибуту (`#[derive(Debug)]`).[1]

## Зачем макросы

Функции Rust имеют фиксированное число аргументов определённых типов. Макросы нужны там, где этого недостаточно:

- **переменное число аргументов** — `println!("{} + {} = {}", a, b, a + b)`, `vec![1, 2, 3]`;
- **проверка на этапе компиляции** — `format!` проверяет, что строка формата совпадает с аргументами;
- **генерация повторяющегося кода** — реализации трейтов для многих типов;
- **встроенные предметные языки** (DSL) — SQL-запросы, HTML-шаблоны, маршруты;
- **автоматическая реализация** [трейтов](https://llmdocs.ru/wiki/rust-traits.md) по структуре типа (`derive`).

## Декларативные макросы

Макросы «по образцу» объявляются через `macro_rules!` и похожи на `match`: каждое правило сопоставляет входные токены с шаблоном и подставляет результат.[2]

```rust
macro_rules! hashmap {
    ($($key:expr => $value:expr),* $(,)?) => {{
        let mut m = ::std::collections::HashMap::new();
        $( m.insert($key, $value); )*
        m
    }};
}

let ages = hashmap! { "alice" => 31, "bob" => 25 };
```

Элементы шаблона:

| Фрагмент | Что совпадает |
|---|---|
| `$x:expr` | выражение |
| `$t:ty` | тип |
| `$i:ident` | идентификатор |
| `$p:pat` | образец |
| `$b:block`, `$s:stmt` | блок, оператор |
| `$tt:tt` | любое дерево токенов |
| `$( ... ),*` | повторение через запятую, 0 и более раз |
| `$( ... );+` | повторение через `;`, 1 и более раз |

Декларативные макросы можно объявить и использовать прямо в одном файле; экспортируют их атрибутом `#[macro_export]`. Сложные трюки вроде рекурсии по токенам описаны в «The Little Book of Rust Macros».[4]

## Процедурные макросы

Процедурный макрос — функция на Rust, которая получает поток токенов (`TokenStream`) и возвращает новый. Такие макросы размещаются в отдельном крейте с `proc-macro = true` в [Cargo.toml](https://llmdocs.ru/wiki/cargo.md) и выполняются компилятором при сборке.[3] Три вида:

**1. Derive-макросы** добавляют реализацию трейта к структуре или перечислению:

```rust
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct User {
    id: u64,
    #[serde(rename = "userName")]
    name: String,
}
```

`Debug`, `Clone`, `PartialEq` встроены в компилятор, а `Serialize` и `Deserialize` предоставляет крейт **Serde**, который генерирует код преобразования в [JSON](https://llmdocs.ru/wiki/json.md) и десятки других форматов без рефлексии во время выполнения.[6]

**2. Атрибутные макросы** преобразуют элемент, к которому применены:

```rust
#[tokio::main]
async fn main() { /* ... */ }

#[get("/users/{id}")]       // actix-web
async fn user(path: web::Path<u64>) -> impl Responder { /* ... */ }
```

`#[tokio::main]` превращает асинхронный `main` в обычный, создающий рантайм (см. [async в Rust и Tokio](https://llmdocs.ru/wiki/rust-async-tokio.md)).

**3. Функционально-подобные макросы** вызываются как `name!(...)`, но, в отличие от `macro_rules!`, могут выполнять произвольный код при компиляции. Пример — `sqlx::query!`, который подключается к базе во время сборки и проверяет SQL-запрос и типы столбцов.

Процедурные макросы почти всегда пишут с помощью крейтов **syn** (разбор токенов в синтаксическое дерево), **quote** (генерация токенов из шаблона) и **proc-macro2**:[5]

```rust
#[proc_macro_derive(Hello)]
pub fn derive_hello(input: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
    let name = &ast.ident;
    quote::quote! {
        impl #name {
            pub fn hello() { println!("Привет от {}", stringify!(#name)); }
        }
    }.into()
}
```

## Отладка

Увидеть, во что раскрывается макрос, позволяет `cargo expand` (обёртка над флагом nightly-компилятора):[7]

```bash
cargo install cargo-expand
cargo expand --lib models::user
```

Ошибки в процедурных макросах лучше выдавать через `syn::Error` с указанием места в исходнике — тогда компилятор подсветит нужный фрагмент кода пользователя, а не внутренности макроса.

## Недостатки

- **Время компиляции** — процедурные макросы и syn заметно замедляют сборку, особенно чистую.
- **Сложность чтения и сопровождения** — макрос скрывает сгенерированный код; IDE (rust-analyzer) поддерживает раскрытие, но не всегда идеально.
- **Сообщения об ошибках** внутри сложных `macro_rules!` бывают малопонятными.
- Процедурные макросы выполняют произвольный код при сборке, что важно для безопасности цепочки поставок — как и `build.rs`.

Правило сообщества: сначала попробовать функции, обобщённые типы и трейты, а макросы использовать, когда без них не обойтись.

## Сравнение с другими языками

Макросы препроцессора C работают с текстом и не гигиеничны. Шаблоны C++ — вычисления над типами. Lisp вдохновил идею макросов над синтаксическим деревом. В [Go](https://llmdocs.ru/wiki/go.md) макросов нет: вместо них используется `go generate` и генерация кода; в [Python](https://llmdocs.ru/wiki/python.md) похожие задачи решают [декораторы](https://llmdocs.ru/wiki/decorators.md) и метаклассы, но во время выполнения.

## См. также

- [Трейты (Rust)](https://llmdocs.ru/wiki/rust-traits.md)
- [Unsafe Rust](https://llmdocs.ru/wiki/unsafe-rust.md)
- [Cargo и crates.io](https://llmdocs.ru/wiki/cargo.md)
- [Clippy и rustfmt](https://llmdocs.ru/wiki/clippy-rustfmt.md)

## Источники

1. [The Rust Programming Language — Macros](https://doc.rust-lang.org/book/ch20-05-macros.html)
2. [The Rust Reference — Macros By Example](https://doc.rust-lang.org/reference/macros-by-example.html)
3. [The Rust Reference — Procedural Macros](https://doc.rust-lang.org/reference/procedural-macros.html)
4. [The Little Book of Rust Macros](https://veykril.github.io/tlborm/)
5. [syn crate documentation](https://docs.rs/syn/)
6. [Serde — Using derive](https://serde.rs/derive.html)
7. [cargo-expand](https://github.com/dtolnay/cargo-expand)
