Rust in Everything
基于 serde 1.0.229

serde 源码解析

全卷只对着一个版本写:

crate版本tagcommit日期
serde1.0.229v1.0.2297fc3b4c2026-07-18
serde_json1.0.154v1.0.15420572a62026-10-11

serde 仓库是一个工作区:serde、serde_core、serde_derive 三个 crate 同版本、同 tag 发布,serde_derive_internals 也在这个工作区里,但有自己的版本号(Cargo.toml 写的是 0.29.1)。serde_json 在另一个仓库,这一卷拿它当「一个格式」的例子。四个 crate 各管什么、谁依赖谁,见源码地图。

serde 要解决的是「M 个类型 × N 种格式」:每个类型要能写成 JSON、YAML、bincode……,每种格式要能读写每个类型。serde 在中间放了一个固定的数据模型,Serializer 的文档写的是「categorize every Rust data structure into one of 29 possible types」(ser/mod.rs)。类型实现 Serialize / Deserialize,只跟数据模型说话;格式实现 Serializer / Deserializer,也只跟数据模型说话。两边都不知道对方是谁,组合数从 M × N 变成 M + N。

这一切都是编译期的:没有运行时反射,#[derive(Serialize, Deserialize)] 是一个过程宏,在编译时为每个类型生成两个 impl;serde_json::to_string::<Post> 被单态化成一份专门处理 Post 的代码。读这一卷,读的就是这两样东西:serde_core 里那几个 trait 怎么分工,以及 serde_derive 为一个 struct 或 enum 生成了什么。axum 卷的 body 提取器和 Path、Query 与请求元数据讲 axum 怎么调 serde_json 和 serde_urlencoded;这一卷讲被调的那一侧。

全卷沿三条线展开:

  • 序列化主线(一次 to_string):derive 生成的 Serialize::serialize 调 serialize_struct("Post", 2),对每个字段 serialize_field,最后 end。serde_json 的 Serializer 在这三步里分别写 {、键值对、},逗号和缩进交给 Formatter。数据从类型推给格式。
  • 反序列化主线(一次 from_str):derive 生成的 Deserialize::deserialize 调 deserialize_struct("Post", FIELDS, __Visitor);serde_json 看到 {,回调 __Visitor::visit_map,后者循环 next_key / next_value,缺字段走 missing_field。格式驱动,类型通过 Visitor 被回调:控制权是反过来的。'de 让 &str 字段直接借用输入。
  • 缓冲主线(untagged、内部标签、flatten):读到决定性的信息之前,不知道该用哪个 Visitor。生成的代码先把整段输入读成一棵 Content 树,再对这棵树重放反序列化。代价是多一次分配和遍历、丢掉格式的类型提示,以及只剩一句话的报错。

三条线每一步对应的源码和页面,见怎么读这一卷。

flowchart TB
  intro[00 导读] --> overview[01 全景]
  overview --> core[02 核心 trait]
  core --> derive[03 派生宏]
  derive --> enums["04 enum 与缓冲"]
  core --> fmt["05 serde_json"]
  enums --> design[06 设计取舍]
  fmt --> design
  design --> pit[07 常见坑]
  pit --> site[08 在本站中]
  site --> hands[09 动手]

实验环境

关于行为的说法(调了哪个方法、按什么顺序、报什么错、写出什么 JSON、rustc 说了什么)都配一个小程序和它的真实输出,平台是 M2 Max、macOS、rustc 1.99.0。serde 没有平台相关的代码路径,只在 macOS 上跑。看 derive 生成的代码用 cargo expand,引用时只摘关键几行。

各部分

  1. 00 导读:锁定的版本,本站锁的 1.0.228 / 1.0.150 到本卷版本的每一处改动;三条主线、阅读路线、怎么看生成代码;工作区四个 crate 的依赖关系、feature、__private229 这个带补丁号的模块名、test_suite/ 的组织。
  2. 01 全景:数据模型为什么能把 M × N 变成 M + N,自描述格式和非自描述格式;一次 to_string 加一次 from_str 的完整往返,用打印调用的手写 Serializer 和 Visitor 对照生成代码。
  3. 02 核心 trait:本卷重点。Serialize 与 Serializer,Deserialize、Deserializer 与 Visitor,SeqAccess / MapAccess / EnumAccess,生命周期 'de,错误,标准库类型的实现,de::value 与 IntoDeserializer。
  4. 03 派生宏:从 syn::DeriveInput 到 const _: () = { ... } 的流水线,属性,生成的 Serialize、生成的 Deserialize,泛型与 bound。
  5. 04 enum 的表示与缓冲:外部标签、内部标签与相邻标签、untagged,Content 和它丢掉的东西,flatten。
  6. 05 一个格式:serde_json:序列化一侧的 Compound 与 Formatter,反序列化一侧的 Read 与借用,Value。
  7. 06 设计取舍:把各章的取舍汇总到一起,每条链回有证据的页面。
  8. 07 常见坑:用报错原文做标题,每个都先跑出最小复现。
  9. 08 在本站中:本站的配置结构体、LLM 客户端的标签 enum、前置元数据怎么用 serde;1.0.228 → 1.0.229 和 serde_json 1.0.150 → 1.0.154 的改动是否影响本站。
  10. 09 动手:不依赖 serde,从零写一个迷你 serde 和一个最小的 #[derive(Serialize)]。

从「导读」开始。只想会用 serde 的读者,读 01 全景、03 里的属性、04 enum 的表示和常见坑;想读懂实现的,按顺序读。02 是全卷的重点:三条主线都从那里的 trait 出发,03 和 05 分别从类型一侧和格式一侧往下走。

💬 关联讨论

发起讨论
加载中...