Макросы в Rust — это не просто строковые подстановки, а полноценные функции, работающие с исходным кодом. Они принимают TokenStream (последовательность токенов, разбитых на TokenTree: идентификаторы, пунктуация, группы и литералы) и возвращают новый TokenStream. В отличие от обычных функций, макросы выполняются на этапе компиляции и могут делать то, что функциям недоступно — например, вставлять break внутрь цикла из макроса unwrap_or_break!.
Декларативные макросы (macro_rules!) используют шаблоны с метапеременными ($e:expr, $i:ident, $b:block). Но для более сложной логики нужны процедурные макросы. Они пишутся в отдельном крейте с флагом proc-macro = true в Cargo.toml. Бывают трёх видов: function-like (вызываются как foo!), derive (#[derive(...)]) и атрибуты (#[attr]). Атрибутный макрос получает два TokenStream: аргументы атрибута и сам элемент кода, и заменяет этот элемент новым кодом.
Работать напрямую с сырыми токенами сложно — надо разбирать синтаксис Rust вручную. Для этого есть крейт syn, написанный Дэвидом Толнеем. Он парсит TokenStream в AST (абстрактное синтаксическое дерево). Верхний узел — syn::File, состоящий из атрибутов и элементов (Item): ItemFn, ItemStruct, ItemConst и т.д. Поля структур в syn идут в том же порядке, что и в исходном коде. Выражения покрывают почти весь язык — от арифметики до match и циклов.
Чтобы превратить AST обратно в TokenStream, используют крейт quote. Его макрос quote! позволяет писать Rust-код с интерполяцией переменных через #var. Это возможно благодаря трейту ToTokens, который реализован для всех типов syn.
Пример из статьи — проектирование макроса bitfields для битовых полей. Пользователь описывает структуру с атрибутами вроде #[flag(r)] или #[flag(dont_shift)], а макрос генерирует getter, setter и clear для каждого поля. Под капотом — простой тип-обёртка (например, SimpleFlagsType(u8)) и битовые операции. Такой подход позволяет абстрагировать работу с флагами на уровне компиляции, создавая удобный DSL прямо в Rust.