域名 AXUM.RS 将于 2025 年 10 月到期。我们无意再对其进行续费,如果你有意接续这个域名,请与我们取得联系。
  • AXUM.RS 现仅需人民币 3000 元(大写:叁仟元整。接受适度议价
  • 按照行业规则,AXUM.RS 到期后,大概率会进入长时间的赎回期,该期间内,如果你想拥有该域名,将要付出高额的费用
  • 我们已启用 AXUM.EU.ORG 域名,并将持续运营
  • 仅接受微信或支付宝交易
如果你对 AXUM.RS 有兴趣,请和我们进行联系:

解析 derive(Db)

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

创建项目及导出过程宏

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

cargo new db-derive --lib

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

加入最需要的3个依赖:

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

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

由于我们是基于 sqlx 进行 CRUD 的,所以将它加入到开发依赖。同时,我们还需要其它一些常用的开发依赖:

[dev-dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "chrono"] }

定义名为Db的 dervie

// 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 代码。

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

本例中:

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

三种过程宏

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

#proc_macro_derive最简单的用法就是 #proc_macro_derive(宏名称),比如我们定义的 #[proc_macro_derive(Db)],是指我们定义了一个名为 Db的 Derive宏,可以这样使用:#[derive(Db)]

使用 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宏。现在通过下面的命令运行:

cargo test --examples ch01-parse-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 目录来进行测试

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