过程宏分类
主要有3种类型过程宏:
-
Function-like procedural macros (类函数宏)
使用
#[proc_macro]
属性声明,并像普通函数一样调用,类似于声明宏。#[proc_macro] pub fn foo(body: TokenStream) -> TokenStream { ... } ... /// use macro foo!( foo bar baz );
-
Custom derive procedural macros (继承宏)
使用
#[proc_macro_derive]
属性声明,用于#[derive]
中并标注在struct/enum
上。#[proc_macro_derive(Bar)] pub fn bar(body: TokenStream) -> TokenStream { ... } ... // use macro #[derive(Bar)] struct S;
-
Custom attributes ****(属性宏)
使用
#[proc_macro_attribute]
声明,并作为属性被调用#[proc_macro_attribute] pub fn baz( attr: TokenStream, item: TokenStream ) -> TokenStream { ... } ... #[baz] fn some_item() {}
过程宏API
主体
我们首先需要明白什么是程序宏的主体:
- 在类函数宏的情况下,主体是圆括号内的一切:
- 在派生宏中,主体是整个被标注的
struct/enum
:
- 对于属性宏,主体包括整个item →
fn some_item() {}
。宏属性本身中也可以有更多的部分作为宏的主体(它们也会作为额外的属性传递给编写的宏函数)。
为了说明这一点,我们将检查一个身份宏,它只是返回它所接受的主体,而不做其他任何事情。
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro]
pub fn foo(body: TokenStream) -> TokenStream {
return body
}
假设我们现在调用 hello()
,hello()在一个 foo!
宏里面。在这种情况下,foo!
将以这样的方式被扩展,看起来就像一开始就没有宏一样:
use my_proc_macro::*;
// foo! {
fn hello() {
println!("Hello, world!");
}
// }
fn main() {
hello();
}
同样,这也可以用属性宏来写:
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn baz(
attr: TokenStream,
item: TokenStream
) -> TokenStream {
return item
}
…
use my_proc_macro::*;
#[baz]
fn hello() {
println!("Hello, world!");
}
fn main() {
hello();
}
Tokens/TokenStream/TokenTree
程序宏的主体被划分为若干片断,称为 Token :
Token 是一个具有特定类型的字符串,在解析宏主体的过程中被赋值。有三种类型的标记:标识符、标点符号和字面量。
程序宏使用来自 proc_macro crate 的特殊数据类型进行操作,proc_macro crate 是标准库的一部分。这些特殊类型之一:TokenTree
,表示可能的标记类型的枚举:
struct TokenStream(Vec<tokentree>);
enum TokenTree {
Ident(Ident),
Punct(Punct),
Literal(Literal),
...
}
</tokentree>
另一个数据结构:TokenStream
,表示 token list,并允许你对 token list 进行迭代(body.into_iter()
)。
TODO!!!
一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。