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