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
管理成有用的东西,然后输出同样有用的东西。 syn 和 quote crate 是这两个任务的当前黄金标准。处理 TokenStream
在《Rust 编程语言》的 宏章节 和 API 文档 中均有介绍。
还有 #[proc_macro]
,它接收以下格式的函数:
#[proc_macro]
pub fn the_name_of_the_macro(input: TokenStream) -> TokenStream
并且可以通过宏名称!(...)
来调用。
proc_macro_derive
,而不是proc_macro_attribute
。 - llogiq