Макросы — это как “шаблоны кода”, которые генерируют код за тебя. Они работают до того, как программа начнет компилироваться. Ты пишешь правило: “если встретишь такую конструкцию, разверни её в такой код”.
В Rust есть два основных типа макросов:
Объявительные макросы — самые простые, создаются через macro_rules!
Процедурные макросы — более мощные, пишутся как полноценный код на Rust.
// Создаем макрос, который выводит "Привет!" 2 раза
macro_rules!hello2{()=>{println!("Привет!");println!("Привет!");};}fnmain(){// Используем макрос — он развернется в 2 строки кода
hello2!();}
Макросы с параметрами
В макросах macro_rules! параметры обозначаются знаком $ и имеют строго определённые типы фрагментов (fragment specifiers). Они говорят компилятору, что именно можно подставить на это место. Макросы в Rust, в отличие от функций, могут принимать разное число параметров.
Таблица сравнения
Тип
Что принимает
Пример
Когда использовать
expr
Выражение
5 + 3, x, foo()
Чаще всего
block
Блок в {}
{ let x = 5; x }
Несколько операторов
ident
Имя
my_var, MyType
Создание имён
ty
Тип
i32, Vec<String>
Параметризация типами
path
Путь
std::collections::HashMap
Доступ к модулям
tt
Дерево токенов
Любой фрагмент кода
Сложная обработка
literal
Литерал
42, "text", true
Константы
vis
Видимость
pub, pub(crate)
Генерация API
item
Элемент
fn foo() {}, struct S
Генерация целых элементов
meta
Атрибут
derive(Debug)
Генерация атрибутов
lifetime
Время жизни
'a, 'static
Работа со ссылками
Важное ограничение
Нельзя использовать expr когда нужно что-то, что обязательно должно заканчиваться точкой с запятой. Для этого есть stmt (но он нестабилен) или block.
expr
Макрос с параметрами expr (выражение, expression): любое валидное выражение Rust, которое возвращает значение:
// Макрос выводит текст несколько раз
macro_rules!повтори{// $раз:expr и $текст:expr - параметры
($раз:expr,$текст:expr)=>{for_in0..$раз{println!("{}",$текст);}};}fnmain(){повтори!(3,"Учу Rust!");// Выведет "Учу Rust!" 3 раза
}
Макрос с вариативными параметрами:
// Макрос, принимающий любое количество чисел
macro_rules!сумма{// Базовый случай: одно число
($a:expr)=>{$a};// Рекурсивный случай: число + остальные
($a:expr,$($rest:expr),*)=>{$a+сумма!($($rest),*)};}fnmain(){lets=сумма!(1,2,3,4,5);println!("Сумма: {}",s);// 15
}
block
Последовательность выражений в фигурных скобках. Отличается от expr тем, что принимает только блок кода, а не любое выражение:
macro_rules!выполнить_блок{($блок:block)=>{println!("Выполняем блок:");letрезультат=$блок;println!("Результат блока: {}",результат);};}fnmain(){выполнить_блок!({leta=5;letb=3;a*b});// ОШИБКА! Это не блок, а просто выражение
// выполнить_блок!(5 + 3);
}
ident
Любой идентификатор языка: имя переменной, функции, типа, модуля.
macro_rules!объяви_переменную{($имя:ident,$тип:ty,$значение:expr)=>{let$имя: $тип=$значение;};}macro_rules!распечатай{($имя:ident)=>{println!("{} = {}",stringify!($имя),$имя);};}fnmain(){объяви_переменную!(x,i32,42);объяви_переменную!(сообщение,String,"Привет".to_string());letвозраст=30;распечатай!(возраст);// выведет: возраст = 30
// ОШИБКА! Это не идентификатор
// распечатай!(5 + 3);
}
ty
Любое обозначение типа в Rust. Нужно, когда макрос должен работать с разными типами данных.
Один токен или группу токенов в скобках. Самый гибкий тип, но и самый сложный. Нужен для сложной обработки синтаксиса, парсинга, создания DSL.
macro_rules!распечатай_tt{($токен:tt)=>{println!("Токен: {}",stringify!($токен));};}macro_rules!повтори_tt{($($т:tt)*)=>{$($т)*$($т)*};}fnmain(){распечатай_tt!(hello);// идентификатор
распечатай_tt!(5);// число
распечатай_tt!(+);// оператор
распечатай_tt!((1+2));// группа в скобках (целое дерево)
letx=повтори_tt!(1+2*3);// станет: let x = 1 + 2 * 3 1 + 2 * 3;
}
path
Полный путь к элементу (с модулями и двоеточиями). Отличие от ident: ident — только одно имя без ::. path — цепочка имён с ::.
macro_rules!используй_функцию{($путь:path,$аргумент:expr)=>{letрезультат=$путь($аргумент);println!("Результат: {}",результат);};}fnмоя_функция(x: i32)-> i32{x*2}modutils{pubfnвспомогательная(x: i32)-> i32{x+10}}fnmain(){используй_функцию!(моя_функция,5);// простое имя
используй_функцию!(utils::вспомогательная,5);// полный путь
используй_функция!(std::cmp::max,5,10);// но это уже два аргумента
// (path не подходит для нескольких)
}
item
Целое определение элемента в Rust (функцию, структуру, impl, модуль и т.д.). Для генерации целых элементов на уровне модуля.
macro_rules!создай_тест{($имя:ident,$тело:item)=>{#[test]fn$имя(){$тело}};}создай_тест!(тест_сложения,{assert_eq!(2+2,4);});// Можно даже целую функцию вставить
создай_тест!(тест_с_функцией,{fnвспомогательная()-> i32{42}assert_eq!(вспомогательная(),42);});
meta
Содержимое атрибутов (для макросов, которые генерируют атрибуты).