过程宏内幕解析「三」

1,566 阅读2分钟

过程宏分类

主要有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

主体

我们首先需要明白什么是程序宏的主体:

  • 在类函数宏的情况下,主体是圆括号内的一切:

image.png

  • 在派生宏中,主体是整个被标注的 struct/enum

image.png

  • 对于属性宏,主体包括整个item → fn some_item() {} 。宏属性本身中也可以有更多的部分作为宏的主体(它们也会作为额外的属性传递给编写的宏函数)。

image.png

为了说明这一点,我们将检查一个身份宏,它只是返回它所接受的主体,而不做其他任何事情。

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 :

image.png

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天,点击查看活动详情