「这是我参与11月更文挑战的第 10 天,活动详情查看:2021最后一次更文挑战」
构建过程宏,要在cargo.toml里面设置一些参数,这是必须的。一般来说,过程宏必须是一个库,或者作为工程的子库,不能单独作为一个源文件存在,至少目前不行。
所以在 cargo new [proc-lib] --lib
中一般需要标明:
[lib]
proc-macro = true
path = "src/lib.rs"
从分类上,可以分为大致3类:
- proc-macro
- proc-macro-derive
- proc-macro-attribute
大致说一下这几种的形态:
格式
简略过一下这几个宏的格式,在后面会具体讲他们的使用范围:
proc-macro
#[proc_macro]
pub fn my_proc_macro(input: TokenStream) -> TokenStream{
// ...
}
可以看出函数式的过程宏只接受一个形参,而且必须是pub的。
proc_macro_derive
#[proc_macro_derive(MyDerive)]
pub fn my_proc_macro_derive(input: TokenStream) -> TokenStream{
// ...
}
proc_macro_derive 表明了这是继承宏,还定义了新的继承宏的名字 MyDerive。 熟悉rust编程的,都应该知道有个继承宏,一直用得到,就是 Debug。这是标准库里的,可以帮助调试和显示。
proc_macro_attribute
#[proc_macro_attribute]
pub fn my_attribute_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
// ...
}
可以看到这里的形参是两个,使用的关键字是 proc_macro_attribute。 关于例子,熟悉python的人应该知道修饰器吧,其实本质就是函数(闭包)可以作为一个对象来返回。 比如我需要一个修饰器来测量一个调用函数的运行时间。
示例
使用代码:
#[handler(blog(::hxl::com))]
fn fake(a: i32) {
println!("hello proc-derive -> fake func args: {}", a);
}
宏代码:
#[proc_macro_attribute]
pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
eprintln!("attr: {:#?}", attr);
eprintln!("item: {:#?}", item);
item
}
cargo check
我们可以在终端中看到:
可以看到目前 attr/item
其实都只是被识别成一个个标志符,没有任何意义,因为要把这些标志符组合有很大的难度。所以需要引入:syn/quote
syn & quote
#[proc_macro_attribute]
pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
let args: AttributeArgs = parse_macro_input!(attr as AttributeArgs);
eprintln!("args: {:#?}", args);
let body = parse_macro_input!(item as Item);
eprintln!("body: {:#?}", body);
quote!(#body).into()
}
parse_macro_input!
-> 是将TokenStream
转换为语法树- 当识别成
AttributeArgs
,输出的是Vec<NestedMeta>
- 当识别成
Item
,输出的是Item
- 当识别成
quote!
-> 可以将语法树及其节点转换为TokenStream
#body_ast
并不属于 Rust 表达式,是quote!
自己实现的一种自定义语法- 返回值是
proc_macro2::TokenStream
,通过into()
才能转换,这个有历史原因 quote/syn/proc_macro2
目的都是为了更方便处理和编辑以及生成proc_macro::TokenStream