解析 derive(Db)

本章我们将开始实现第一步:解析 derive(Db)

首先,我们创建一个 lib 项目:

cargo new db-derive --lib

接着,我们需要做最重要的一步:导出过程宏。打开 Cargo.toml,增加以下配置:

[lib]
proc-macro = true

加入最需要的3个依赖:

[dependencies]
syn = { version = "2", features = ["extra-traits"] }
quote = "1"
proc-macro2 = "1"

synextra-traits 可以让 syn 的数据实现 Debug,以便于我们调试。开发完成后,可以将此 feature 去除。

定义名为Db的 dervie

打开 lib.rs ,新建一个 db_derive 函数(函数名随意,只是为了明确语义,我们选择了这个函数名):

// src/lib.rs

#[proc_macro_derive(Db)]
pub fn db_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    println!("{:#?}", input);
    proc_macro::TokenStream::default()
}

这个函数非常简单:接收一个 proc_macro::TokenStream,然后返回一个 proc_macro::TokenStreamproc_macro::TokenStream 可以理解为代码的抽象树,它可以构成合法的 rust 代码。

  • input 参数是指,应用到该 derive 的实体(比如结构体)的完成代码抽象树
  • 返回值是指,如何处理 input ,并根据需要生成新的代码抽象树

注意,要使用 proc_macro::TokenStream,必须加上 #[proc_macro], #[proc_macro_attribute]#[proc_macro_derive]

本例中:

  • 我们只是简单的打印了实体的代码抽象树:println!("{:#?}", input);
  • 然后返回了一个空的抽象树:proc_macro::TokenStream::default()

三种过程宏

Rust 的过程宏支持三种过程宏,分别是:

  • #[proc_macro]:函数形式的过程宏。使用方式类似我们常用的 println!()以及用声明宏(即使用 macro_rules!)定义的宏。
  • #[proc_macro_attribute]:属性宏。使用方式类似于 #[cfg(test)]
  • #[proc_macro_derive]:Derive 宏。本专题要讲述的内容,使用方式类似于:#[derive(Debug)]。Derive 宏也可以定义属性,所以对于专题来讲,该宏符合我们的需求。

使用 Db

在项目根目录下(和src 目录同级)创建一个 examples目录,用于测试我们的宏。首先,创建 examples/ch01-parse-derive.rs 文件,并输入以下内容:

// examples/ch01-parse-derive.rs

use db_derive::Db;

#[derive(Db)]
pub struct User {
    pub id: String,
    pub email: String,
    pub password: String,
    pub nickname: String,
    pub dateline: chrono::DateTime<chrono::Local>,
}

fn main() {}

我们定义了一个 User 结构体,并使用了我们刚刚创建的 Db Derive宏。现在通过下面的命令运行:

神奇的一幕发生了,我们看到 User 结构体的抽象树被打印出来了:

TokenStream [
    Ident {
        ident: "pub",
        span: #0 bytes(34..37),
    },
    Ident {
        ident: "struct",
        span: #0 bytes(38..44),
    },
    Ident {
        ident: "User",
        span: #0 bytes(45..49),
    },
    Group {
        delimiter: Brace,
        stream: TokenStream [
            Ident {
                ident: "pub",
                span: #0 bytes(56..59),
            },
            Ident {
                ident: "id",
                span: #0 bytes(60..62),
            },
            Punct {
                ch: ':',
                spacing: Alone,
                span: #0 bytes(62..63),
            },
            Ident {
                ident: "String",
                span: #0 bytes(64..70),
            },
            Punct {
                ch: ',',
                spacing: Alone,
                span: #0 bytes(70..71),
            },
            // ...
        ],
        span: #0 bytes(50..199),
    },
]

从打印的结果不难看出,结构体本身的名字、所有字段的名字、可见性及数据类型都打印出来了。

你也可以通过创建 tests 目录来进行测试

本章代码位于01/解析derive分支。

要查看完整内容,请先登录