На главную > Блог > Категория > ⚙️ Процедурные макросы в Rust
Rust procedural macros metaprogramming custom derive proc-macro
Процедурные макросы — одна из самых мощных фич Rust. Они позволяют не просто генерировать код по шаблону, а анализировать синтаксическую структуру Rust-кода на этапе компиляции и изменять её или расширять [citation:8]. В отличие от декларативных макросов (macro_rules!), которые работают как «умная замена» по шаблонам, процедурные макросы — это полноценные Rust-функции, которые принимают на вход поток токенов (TokenStream) и возвращают новый поток токенов [citation:1][citation:6][citation:8].
Процедурные макросы делятся на три типа, которые различаются только синтаксисом вызова и тем, что они обрабатывают на входе [citation:6][citation:8].
#[derive(MyTrait)]. Задача такого макроса — автоматически сгенерировать реализацию нужного трейта для этого типа [citation:1][citation:6][citation:8].#[my_attr] или #[my_attr(…)] [citation:6][citation:8].my_macro!(…). В отличие от декларативных макросов, они могут принимать произвольные токены и обрабатывать их в полной мере, а не только по шаблону [citation:6][citation:8].syn, формировать новый код с помощью quote и даже отлаживать генерацию с помощью cargo expand [citation:3][citation:6].
Процедурные макросы всегда живут в отдельном крейте, который должен быть помечен как proc-macro в Cargo.toml [citation:1][citation:6][citation:8].
[lib]
proc-macro = true
[dependencies]
syn = { version = "2", features = ["full", "derive"] }
quote = "1"
В коде самого макроса используется базовая структура [citation:3][citation:6]:
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
gen.into()
}
parse_macro_input! — разбирает входной поток токенов в структуру AST с помощью syn [citation:3][citation:6].quote! — формирует новый код, подставляя значения из переменных через #name [citation:3][citation:6].stringify!(#name) — превращает имя типа в строку для вывода [citation:3].
Если ваш макрос должен принимать пользовательские атрибуты, их нужно явно перечислить в атрибуте proc_macro_derive [citation:3].
#[proc_macro_derive(HelloMacro, attributes(HelloMacroName))]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
// ... разбор атрибута HelloMacroName
}
derive к enum, а макрос работает только со структурами), используйте panic! с понятным сообщением [citation:3]. Компилятор перехватит панику и покажет её как ошибку компиляции.
Более продвинутый способ — использовать syn::Error с правильной привязкой к позиции в коде [citation:6].
DeriveInput есть поля для параметров типа, лайфтаймов и констрейнов. Используйте ast.generics.split_for_impl(), чтобы корректно сгенерировать реализацию для обобщённых типов [citation:6].trybuild можно проверить, что макрос работает правильно на позитивных примерах (pass) и выдаёт ожидаемые ошибки на негативных (compile_fail) [citation:6].darling: Если макрос принимает сложные атрибуты, крейт darling позволяет автоматически парсить их в структуру с валидацией [citation:6].::std::string::String), чтобы избежать конфликтов с именами в области видимости вызывающего кода [citation:6].Процедурные макросы — это «кузница» кода в Rust. Они позволяют делать с кодом то, что на других языках требует тяжеловесных кодогенераторов. Связка proc_macro + syn + quote даёт разработчикам возможность анализировать и генерировать код прямо во время компиляции, а cargo expand и trybuild — отлаживать этот процесс [citation:3][citation:6].
Материалы для углублённого изучения: книга «Write Powerful Rust Macros» Сэма Ван Овермейра (Manning, 2024) полностью посвящена этой теме — от первых шагов до проектирования DSL и публикации собственных макросов [citation:9].
Статья создана для образовательных целей. Код из примеров приведён в упрощённом виде и не является готовым к использованию в продакшене.
Дата размещения статьи: 26-07-2026 в 13:41:29