Описание тега rust-proc-macros
Компилятор Rust (rustc
) может принимать различные плагины, называемые процедурными макросами, для изменения кода пользователя и генерации его во время компиляции.
Документацию можно найти в официальной книге.
Виды процедурных макросов
Есть 3 вида процедурных макросов.
По обычаю происходит
Они используются следующим образом:
#[derive(MyCustomDerive)]
struct Foo {
// ...
}
Этот вид макросов применяется к декорированным struct
или enum
и предназначены для генерации некоторого кода без изменения кода пользователя, обычно для генерации реализации признака.
Примеры таких макросов есть в std
ящик, например, чтобы сгенерировать реализацию Debug
или PartialEq
черты.
Настраиваемые атрибуты
Они используются следующим образом:
#[my_custom_attribute(optional_parameter = "value")]
fn some_function() {
// ...
}
Они могут произвольно изменять любой элемент Rust: struct
, функция, модуль и т. д.
Функциональный макрос
Они используются следующим образом:
my_function_like_macro!(some arbitrary input);
Он может принимать любой ввод для генерации некоторого кода Rust: единственным ограничением формата ввода является воображение разработчика макроса.
Как создать процедурный макрос
Процедурный макрос должен жить в собственном ящике. В строке необходимо добавить специальную строку.Cargo.toml
манифест:
[lib]
proc-macro = true
Следующие зависимости не являются обязательными, но широко используются из-за полезных вещей, которые они приносят:
proc-macro2
позволь использовать нестабильнуюproc-macro
вещи внутри проекта, скомпилированного в stable;quote
позвольте легко сгенерировать некоторый код Rust;syn
является парсером исходного кода на Rust.
Макросы должны быть объявлены в корневом модуле (т. Е. Вlib.rs
файл), например, для настраиваемого атрибута:
#[proc_macro_attribute]
pub fn fact_inner(args: TokenStream, input: TokenStream) -> TokenStream {
/// ...
}
Каждый процедурный макрос возвращает TokenStream
являющийся сгенерированным кодом.
Функции для каждого типа процедуры имеют разную сигнатуру:
Пользовательское получение
Пользовательское производное должно быть объявлено следующим образом:
#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
// ...
}
input
определяется пользователем struct
или enum
украшается.
Типичная реализация будет выглядеть следующим образом:
#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
// Parse the input as a struct:
let item = syn::parse_macro_input!(input as syn::ItemStruct);
// Or if you want to decorate an enum:
let item = syn::parse_macro_input!(input as syn::ItemEnum);
// Get the generated code, or transform an error into a compile error:
let output = my_derive_generate(item).unwrap_or_else(|err| err.to_compile_error());
TokenStream::from(output)
}
fn my_derive_generate(input: syn::ItemStruct) -> Result<proc_macro2::TokenStream, syn::parse::Error> {
// ...
}
Вы можете принимать атрибуты в определяемом пользователем коде:
#[proc_macro_derive(MyDerive, attributes(my_attribute))]
pub fn my_derive(input: TokenStream) -> TokenStream {
// ...
}
Пример принятого кода от пользователя:
#[derive(MyDerive)]
struct Foo {
#[my_attribute("any content")]
bar: i32,
}
Настраиваемый атрибут
Он должен быть объявлен так:
#[proc_macro_attribute]
pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
// ...
}
args
- аргументы атрибута, а input
- ввод пользователя, к которому применяется макрос.
Вот допустимое содержимое атрибута:
- Без содержания:
#[attribute]
- Имя = значение:
#[attribute = "a literal"]
- Буквальный:
#[attribute("a literal")]
- Список:
#[attribute(Ident1, Ident2)]
- Список ключей / значений:
#[attribute(key = literal1, key = literal2)]
Типичная реализация будет выглядеть следующим образом:
#[proc_macro_attribute]
pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
// Parse the attribute arguments:
let args = syn::parse_macro_input!(args as syn::AttributeArgs);
// Parse the input (for example):
let item = syn::parse_macro_input!(input as syn::ItemFn);
// Get the generated code, or transform an error into a compile error:
let output = my_attribute_generate(item).unwrap_or_else(|err| err.to_compile_error());
TokenStream::from(output)
}
fn my_attribute_generate(args: syn::AttributeArgs, input: syn::ItemStruct)
-> Result<proc_macro2::TokenStream, syn::parse::Error> {
// ...
}
Функциональный макрос
Он должен быть объявлен так:
#[proc_macro]
pub fn my_proc_macro(input: TokenStream) -> TokenStream {
// ...
}