- 支持试读
内容介绍
本专题将通过实现一个自动生成数据库 CRUD 的 Derive宏 来对过程宏 `proc-macro` 进行一步步的探讨。我们希望通过本专题的学习,能让你掌握 rust 过程宏的知识要点,并将其应用到实际开发中。 - 支持试读
解析 derive(Db)
本章我们将开始实现第一步:解析 `derive(Db)`。 - 支持试读
解析目标结构体的元数据
本章我们将讨论如何解析目标结构体的元数据,包括:结构体的名称、结构体的字段(包括可见性、字段名和数据类型)。 解析Derive和字段的属性,并实现 CRUD 操作中的插入
本章我们将实现 CRUD 操作中的【插入】。在实际开发中,有些字段是不需要插入的,比如自动编号的主键。我们可以通过宏属性来指定哪些字段不需要插入。同时,我们还要通过宏属性来指定目标结构体的表名、主键以及是否为视图等。- 支持试读
实现更新和删除方法
有了上一章的基础,我们实现更新和删除方法也不是难事。 实现单条数据的查找
本章我们将讨论如何用宏为目标结构体实现单条数据的查找。实现数据列表和分页
本章我们将讨论如何用宏为目标结构体实现数据列表和分页。
解析 derive(Db)
创建项目及导出过程宏
cargo new db-derive --lib
接着,我们需要做最重要的一步:导出过程宏。打开 Cargo.toml
,增加以下配置:
[lib]
proc-macro = true
加入最需要的3个依赖:
[dependencies]
syn = { version = "2", features = ["extra-traits"] }
quote = "1"
proc-macro2 = "1"
syn
的extra-traits
可以让syn
的数据实现Debug
,以便于我们调试。开发完成后,可以将此 feature 去除。
syn
的 extra-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
打开 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::TokenStream
。proc_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 宏也可以定义属性,所以对于专题来讲,该宏符合我们的需求。
#proc_macro_derive
最简单的用法就是 #proc_macro_derive(宏名称)
,比如我们定义的 #[proc_macro_derive(Db)]
,是指我们定义了一个名为 Db
的 Derive宏,可以这样使用:#[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() {}
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
目录来进行测试
你也可以通过创建 tests
目录来进行测试
本章代码位于01/解析derive分支。