Как мне создать proc_macro_attribute?

Теперь, когда proc_macros стабилизировался, как можно создать такую ​​вещь?

Из того, что я видел, есть возможность поставить #[proc_macro_attribute] аннотация на fn whatsitsname(attrs: TokenStream, code: TokenStream) -> TokenStream, но как я могу это зарегистрировать? Как я могу добавить пользовательские атрибуты?

2 ответа

Решение

Компилятор Rust имеет довольно полный набор тестов. Когда я ищу примеры новых функций, я часто начинаю с них:

$ rg -c proc_macro_attribute
src/test/run-pass-fulldeps/auxiliary/proc_macro_def.rs:2
src/test/ui-fulldeps/auxiliary/attr_proc_macro.rs:1
[... 35 other matches ...]

Вот полностью проработанный пример:

$ tree
.
├── Cargo.toml
├── my_macro
│   ├── Cargo.toml
│   ├── src
│   │   └── lib.rs
└── src
    └── main.rs

Cargo.toml

Мы добавляем зависимость от нашего макро-определяющего ящика.

[package]
name = "foo"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[dependencies]
my_macro = { path = "my_macro" }

SRC /main.rs

Мы импортируем макрос атрибута и добавляем его в функцию.

#[macro_use]
extern crate my_macro;

#[log_entry_and_exit(hello, "world")]
fn this_will_be_destroyed() -> i32 {
    42
}

fn main() {
    dummy()
}

my_macro/ Cargo.toml

Мы уточняем crate_type как proc_macro,

[package]
name = "my_macro"
version = "0.1.0"
authors = ["An Devloper <an.devloper@example.com>"]

[lib]
crate_type = ["proc-macro"]

my_macro/ SRC / lib.rs

Мы добавляем #[proc_macro_attribute] для каждой функции, которая должна быть макросом.

extern crate proc_macro;

use proc_macro::*;

#[proc_macro_attribute]
pub fn log_entry_and_exit(args: TokenStream, input: TokenStream) -> TokenStream {
    let x = format!(r#"
        fn dummy() {{
            println!("entering");
            println!("args tokens: {{}}", {args});
            println!("input tokens: {{}}", {input});
            println!("exiting");
        }}
    "#,
            args = args.into_iter().count(),
            input = input.into_iter().count(),
    );

    x.parse().expect("Generated invalid tokens")
}

грузовой пробег

entering
args tokens: 3
input tokens: 7
exiting

"Тяжелая" часть TokenStream в нечто полезное, а затем выводить что-то одинаково полезное. Ящики син и цитата являются текущими золотыми стандартами для этих двух задач. Имея дело с TokenStream рассматривается в приложении к макросам языка программирования Rust, а также в документации по API.

Есть также #[proc_macro], который принимает функции вида:

#[proc_macro]
pub fn the_name_of_the_macro(input: TokenStream) -> TokenStream

И может быть вызван как the_name_of_the_macro!(...),

Если я правильно понимаю RFC 1566, вы:

  • Создать ящик типа proc_macroто есть Cargo.toml должен содержать

    [lib]
    proc-macro = true
    
  • В этом ящике создайте реализацию, помеченную #[proc_macro_attribute], #[proc_macro] для функциональных макросов и #[proc_macro_derive] для пользовательских производных работают одинаково, за исключением того, что они имеют только один TokenStream аргумент. Они определены в proc_macro обрешетка.

    Первый поток токенов - это аргументы в атрибуте, второй - тело аннотированного элемента.

  • Затем в ящике, который хочет использовать макрос, просто положитесь на ящик proc_macro и импортируйте его с помощью #[macro_use] атрибут (#[macro_use] extern crate...).

Этого должно быть достаточно.

Приложение в Книге должно быть расширено, чтобы упомянуть другие типы макросов за пределами #[proc_macro_derive], Это, вероятно, ошибка.

Другие вопросы по тегам