Процедурный макрос позволяет вставлять произвольный код Rust в исходный код программы. При этом входные данные для макроса представляют проанализированныме токенам, соответствующим некоторому фрагменту исходного исходного кода.
Процедурные макросы должны размещаться в отдельном крейте, который должен иметь тип proc-macro. Есть 3 типа процедурных макросов:
Макросы-функции. Вызываются подобно функциям с передачей им аргументов
Макросы-атрибуты. Прикрепляются к некоторому фрагменту синтаксиса в программе
Derive-макросы. Прикрепляются к определению структуры данных
РАссмотрим функциональные макросы. Для этого создадим новый проект cargo, например, он будет называться macroapp:
cargo new macroapp
В созданном проекте изменим файл Cargo.toml:
[package] name = "macroapp" version = "0.1.0" edition = "2021" [lib] proc-macro = true [dependencies]
Здесь добавлена секция [lib], в которой задан тип крейта proc-macro = true
Далее в папку src добавим новый файл lib.rs, где определим следующий код:
use proc_macro::TokenStream;
#[proc_macro]
pub fn my_func_macro(args: TokenStream) -> TokenStream {
println!("Поток токенов:");
for tt in args {
println!(" {tt:?}");
}
// возвращаем пустой поток токенов
TokenStream::new()
}
Функциональный макрос предваряется атрибутом #[proc_macro]. В качестве параметра функция макроса получает поток токенов - TokenStream. В данном случае просто выводим токены на консоль.
В файле main.rs в папке src протестируем макрос:
use macroapp::my_func_macro;
fn main() {
my_func_macro!(let x = (n)=> n*n;);
}
Здесь в макрос передается произвольный код - let x = (n)=> n*n; (не столь важно, какой это код - языка Rust или нет, валидный или нет). На уровне макроса этот код превратится в поток токенов. B ghb pfgecrt
проекта с помощью cargo run мы получим следующий вывод:
Поток токенов:
Ident { ident: "let", span: #0 bytes(63..66) }
Ident { ident: "x", span: #0 bytes(67..68) }
Punct { ch: '=', spacing: Alone, span: #0 bytes(69..70) }
Group { delimiter: Parenthesis, stream: TokenStream [Ident { ident: "n", span: #0 bytes(72..73) }], span: #0 bytes(71..74) }
Punct { ch: '=', spacing: Joint, span: #0 bytes(74..75) }
Punct { ch: '>', spacing: Alone, span: #0 bytes(75..76) }
Ident { ident: "n", span: #0 bytes(76..77) }
Punct { ch: '*', spacing: Alone, span: #0 bytes(77..78) }
Ident { ident: "n", span: #0 bytes(78..79) }
Punct { ch: ';', spacing: Alone, span: #0 bytes(79..80) }