如何创建 proc_macro_attribute?

How do I create a proc_macro_attribute?

现在proc_macros have been stabilized,如何创造这样的东西?

据我所知,可以选择在 fn whatsitsname(attrs: TokenStream, code: TokenStream) -> TokenStream 上添加 #[proc_macro_attribute] 注释,但我该如何注册呢?如何添加自定义属性?

如果我理解 RFC 1566 正确,您:

  • 创建一个 proc_macro 类型的箱子,即它的 Cargo.toml 应该包含

    [lib]
    proc-macro = true
    
  • 在那个板条箱中,创建实现,用 #[proc_macro_attribute] 注释。类函数宏的 #[proc_macro] 和自定义派生的 #[proc_macro_derive] 工作相同,只是它们只有一个 TokenStream 参数。这些在 proc_macro crate 中定义。

    第一个标记流是属性中的参数,第二个是注释项的主体。

  • 然后在要使用宏的箱子中,只需依赖 proc_macro 箱子和 使用 #[macro_use] 属性导入它 (#[macro_use] extern crate…).

应该够了。

应该扩展 appendix in Book 以提及 #[proc_macro_derive] 之外的其他 proc 宏类型。它没有可能是一个错误。

Rust 编译器具有相当完整的test suite。在查找新引入的功能示例时,我经常从这里开始:

$ 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_typeproc_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

"hard" 部分正在将 TokenStream 整理成有用的东西,然后输出同样有用的东西。板条箱 syn and quote are the current gold standards for those two tasks. Dealing with TokenStream is covered in the macros chapter of The Rust Programming Language as well as API documentation.

还有 #[proc_macro],它采用以下形式的函数:

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

并且可以调用为 the_name_of_the_macro!(...)