我该如何创建一个proc_macro_attribute?

14
现在proc_macros已经稳定,那么如何创建这样一个东西呢?
从我所见,有一种选项是在fn whatsitsname(attrs: TokenStream, code: TokenStream) -> TokenStream上放置#[proc_macro_attribute]注释,但是我该如何注册它?我该如何添加自定义属性?

1
就它的价值而言,这本书包含了一节关于编写过程宏的部分。链接:https://doc.rust-lang.org/book/2018-edition/appendix-04-macros.html#procedural-macros-for-custom-derive。 - Sven Marnach
1
@SvenMarnach 这只解释了 proc_macro_derive,而不是 proc_macro_attribute - llogiq
@llogiq 我知道这一点,但是这个链接可能对其他来到这里的人有用。(而你的第一个问题是“如何创建这样的东西”,这在那里已经涵盖了。) - Sven Marnach
我认为它们应该在@SvenMarnach链接的书附录中有所记录,如果没有记录,则应该报告错误,并在跟踪问题https://github.com/rust-lang/rust/issues/38356中引用(它仍然是开放的,并且没有提到记录该功能,但可能应该)。 - Jan Hudec
2个回答

21
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")
}

cargo run

entering
args tokens: 3
input tokens: 7
exiting

“硬”部分是将 TokenStream 管理成有用的东西,然后输出同样有用的东西。 synquote crate 是这两个任务的当前黄金标准。处理 TokenStream 在《Rust 编程语言》的 宏章节API 文档 中均有介绍。

还有 #[proc_macro],它接收以下格式的函数:

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

并且可以通过宏名称!(...)来调用。


3

如果我正确理解RFC 1566,那么你应该:

  • 创建一个类型为proc_macro的板条箱,即其Cargo.toml应包含
[lib]
proc-macro = true
  • 在那个 crate 中,创建一个带有 #[proc_macro_attribute] 注解的实现。对于类似函数的宏和自定义派生的 #[proc_macro]#[proc_macro_derive] 的工作方式相同,只是它们只有一个 TokenStream 参数。这些都在 proc_macro crate 中定义。

    第一个 token stream 是属性中的参数,第二个是注释项的主体。

  • 然后,在想要使用宏的 crate 中,只需依赖于 proc_macro crate 并使用 #[macro_use] 属性导入它(#[macro_use] extern crate …)。

应该就够了。

书中附录 应该扩展以提到除 #[proc_macro_derive] 之外的其他 proc 宏类型。它没有这样做可能是一个 bug。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接