Описание тега rust-proc-macros

Используйте этот тег для вопросов о процедурных макросах, объявленных в ящиках типа proc-macro языка программирования Rust.

Компилятор 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 {
    // ...
}