- 支持试读
内容介绍
本专题将通过实现一个自动生成数据库 CRUD 的 Derive宏 来对过程宏 `proc-macro` 进行一步步的探讨。我们希望通过本专题的学习,能让你掌握 rust 过程宏的知识要点,并将其应用到实际开发中。 - 支持试读
解析 derive(Db)
本章我们将开始实现第一步:解析 `derive(Db)`。 - 支持试读
解析目标结构体的元数据
本章我们将讨论如何解析目标结构体的元数据,包括:结构体的名称、结构体的字段(包括可见性、字段名和数据类型)。 解析Derive和字段的属性,并实现 CRUD 操作中的插入
本章我们将实现 CRUD 操作中的【插入】。在实际开发中,有些字段是不需要插入的,比如自动编号的主键。我们可以通过宏属性来指定哪些字段不需要插入。同时,我们还要通过宏属性来指定目标结构体的表名、主键以及是否为视图等。- 支持试读
实现更新和删除方法
有了上一章的基础,我们实现更新和删除方法也不是难事。 实现单条数据的查找
本章我们将讨论如何用宏为目标结构体实现单条数据的查找。实现数据列表和分页
本章我们将讨论如何用宏为目标结构体实现数据列表和分页。
解析 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"
syn
的extra-traits
可以让syn
的数据实现Debug
,以便于我们调试。开发完成后,可以将此 feature 去除。
[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_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),
},
]
从打印的结果不难看出,结构体本身的名字、所有字段的名字、可见性及数据类型都打印出来了。
本章代码位于01/解析derive分支。