Serde 元编程:从过程宏到零成本抽象

从编译期元编程视角剖析 Serde 的工程实现。

几乎每一个 Rust 项目都依赖 Serde。它的使用极其朴素——写一行 #[derive(Serialize, Deserialize)],数据结构就能在 JSON、YAML、TOML、Bincode、MessagePack 之间流转。但在这行轻量语法背后,隐藏着 Rust 生态最精巧的工程设计之一:一套由过程宏、trait、泛型和单态化共同构建的零成本抽象。

本专栏不教读者"怎么用 Serde"。本专栏拆解 Serde 源码——从 syn 解析 AST、quote 生成 TokenStream,到 Data Model 如何把"M 种数据结构 × N 种格式"的组合爆炸降为 M+N 的实现复杂度;从 Serializer/Deserializer trait 的边界设计,到反序列化为什么需要 Visitor 模式;从属性宏 #[serde(rename)] 的解析机制,到借用反序列化如何在编译期证明生命周期安全。

这是 Rust 后端系列的第三个专栏。读完本专栏,你不只学会了 Serde——你掌握了 Rust 过程宏这门"元能力"。再看 Axum 的 #[derive(FromRequest)]、sqlx 的 query!、clap 的 #[derive(Parser)]、tokio 的 #[tokio::main],你会发现它们共享同一套工程范式。

flowchart TB
  D["#[derive(Serialize, Deserialize)]"] --> PM
  subgraph PM [过程宏 · 编译期运行的 Rust 函数]
    direction LR
    TS[TokenStream] --> SYN[syn<br/>解析成 AST] --> ATTR["属性系统<br/>#[serde(...)]"]
    ATTR --> QT[quote<br/>生成代码]
  end
  QT --> CG["codegen<br/>serialize_body 五路分派"]
  CG --> IMPL[你的类型的 impl]
  IMPL --> DM
  subgraph DM [Data Model · 把 M×N 降成 M+N]
    direction LR
    SER["Serializer trait<br/>30 个 serialize_* 方法 · self 是 move"] --- DE[Deserializer + Visitor]
  end
  DM --> FMT[JSON · YAML · TOML<br/>Bincode · MessagePack]

Serde 真正的设计眼光在中间那一格。 M 种数据结构 × N 种格式本来是组合爆炸,Data Model 把它降成 M+N —— 每个类型只需实现一次 Serialize / Deserialize,每个格式只需实现一次 Serializer / Deserializer。这个抽象值多少,看一眼 Rust 生态里格式库的数量就知道了。

读完你能做到什么

  1. 理解 Data Model 这个中间层。 29 种原语的划分依据、为什么它能把组合爆炸降维、边界在哪(第2章 Data Model)。
  2. 读懂两个核心 trait 的设计取舍。 Serializer 的 30 个 serialize_* 方法怎么组织、self 为什么是 move 语义、默认实现的性能兜底;以及反序列化为什么需要 Visitor 模式(第3章 Serializer、第4章 Deserializer 与 Visitor)。
  3. 自己写一个 derive 宏。 宏的三种形态、过程宏的运行时机、syn 解析 AST、quote 的 quasi-quoting 与 #var 插值、ToTokens、#(...)* 重复语法,然后动手写出第一个 derive(第5章 宏系统全景、第6章 TokenStream、第7章 syn、第8章 quote、第9章 第一个 derive)。
  4. 看懂 Serde 自己的代码生成。 derive 的整体架构、属性系统怎么解析、expand_derive_serialize 的全貌、serialize_body 的五路分派,以及各种 struct/enum 形态各自生成什么(第10章 derive 架构、第11章 属性系统、第12章 序列化 codegen、第13章 反序列化 codegen)。
  5. 处理 enum 与借用这两个难点。 四种 enum 标签表示(externally / internally / adjacently / untagged)各自的取舍,以及借用反序列化如何在编译期证明生命周期安全(第14章 enum 标签、第15章 借用反序列化)。
  6. 用好高级属性并读懂一个真实格式实现。 进阶属性组合,以及 serde_json 是怎么落地这套抽象的(第16章 高级属性、第17章 serde_json、第18章 设计哲学)。

适合谁读

前置阅读:本专栏假设读者已经能写 Rust 代码,理解 trait、泛型、生命周期的基本概念。如果对这些机制的编译期实现尚有疑问,建议先读《Rust 编译器与运行时揭秘》专栏。本专栏不依赖 Tokio 相关知识,可独立阅读。

目录

第一部分:开篇

第二部分:Data Model 与 trait 抽象

第三部分:过程宏工程学

第四部分:serde_derive 源码剖析

第五部分:高阶主题

第六部分:生态与总结

源码版本

本专栏所有源码引用均基于以下版本(2026 年 4 月 20 日锁定;proc-macro2 与 rust-lang/rust 两行为后来补入,其版本发布于锁定日之后):

Crate 版本 Git Commit
serde 1.0.228 fa7da4a93567ed347ad0735c28e439fca688ef26
serde_json 1.0.149 dc8003a88e7142529cf4a7429c4778af31dadf50
syn 2.0.117 e027fef2adcd6588fa8097c63dc916778d1aa11b
quote 1.0.45 ba07807af385afeff97a5e75186f7a92eb629a79
proc-macro2 1.0.107 ed8a5497669cd63db33bf24646f261b012bbbc4a
sqlx 0.8.6 bab1b022bd56a64f9a08b46b36b97c5cff19d77e(对照用:第 5 / 7 / 8 章拿 sqlx::query! 作对照,不属于 serde 生态)
rust-lang/rust 1.96.0 tag 1.96.0(第 5–8 章引到 rustc 源码,如第 5 章讲 fragment specifier 时的 compiler/rustc_ast/src/token.rs 与 compiler/rustc_feature/src/unstable.rs)

表中版本号取各仓库 Cargo.toml。其中 serde、serde_json、syn、quote 的 commit 并非 release tag 本身,而是 tag 之后的主线提交(分别领先 31 / 7 / 16 / 1 个提交),与 crates.io 上的同号发布版可能有个别行号出入;proc-macro2 的 commit 就是 1.0.107 的发布提交。

读者可通过以下命令获取与本专栏完全一致的源码:

git clone https://github.com/serde-rs/serde.git
git -C serde checkout fa7da4a

git clone https://github.com/serde-rs/json.git serde_json
git -C serde_json checkout dc8003a

git clone https://github.com/dtolnay/syn.git
git -C syn checkout e027fef

git clone https://github.com/dtolnay/quote.git
git -C quote checkout ba07807

git clone https://github.com/dtolnay/proc-macro2.git
git -C proc-macro2 checkout ed8a549

git clone https://github.com/launchbadge/sqlx.git
git -C sqlx checkout bab1b02

正文每一段源码引用都会标注文件路径和行号,读者可在对应版本的代码中逐行验证。

版权声明

本专栏内容为 杨艺韬 版权所有,保留一切权利。未经书面许可,不得全文或大段转载、改编、翻译,或用于任何商业用途(含以本专栏内容训练模型、生成衍生课程或商品)。

欢迎分享本专栏的链接。引用少量内容用于评论、教学或研究时,请署名 杨艺韬 并附上原文链接。