Appearance
第 13 章 Deserialize 代码生成:Visitor 的自动构造
13.1 反序列化为什么是 Serde 最难的章节
第 12 章讲 Serialize 代码生成——结构直接:遍历字段、调 Serializer 方法、组装完事。1369 行代码看起来不少,但核心模式可以用一句话总结:"for each field, emit a serialize_field call"。
Deserialize 完全不同。看 serde_derive 的目录:
serde_derive/src/
├── ser.rs 1369 行(Serialize,单文件)
├── de.rs 976 行(Deserialize 主文件)
└── de/
├── struct_.rs 697 行
├── tuple.rs 283 行
├── unit.rs 52 行
├── enum_.rs 96 行(enum 分发)
├── enum_externally.rs 213 行
├── enum_internally.rs 106 行
├── enum_adjacently.rs 324 行
├── enum_untagged.rs 135 行
└── identifier.rs 477 行反序列化代码一共 3359 行,是 Serialize 的 2.5 倍。 分散在 10 个文件里,每个文件处理一种特定场景。为什么?
原因在第 4 章讲过:Deserialize 要通过 Visitor 模式反向控制。每一种类型的反序列化都要:
- 定义一个 Visitor(一个临时 struct)
- 给 Visitor 实现
Visitortrait,写expecting+ 若干visit_*方法 - 为 struct 反序列化额外实现字段名识别(一个"枚举字段"+ 它的 Visitor)
- 处理"可能借用输入数据"的生命周期参数
'de - 处理 enum 的 4 种 tag 模式 × 4 种 variant style
每一项都需要一个独立的代码生成路径。单个 struct 的 Deserialize 生成代码可能有 80-200 行(Serialize 通常 30-50 行)。
本章不试图把全部 3359 行讲完——那需要一本小书。我们聚焦 3 个代表性场景:
- 普通 struct 的 Deserialize 生成(最基础,展示 Visitor 模式如何在代码中出现)
- 字段名识别(
identifier.rs的核心——一个独立的小 Visitor) - 生命周期与借用反序列化(
'de如何在生成代码里出现)
第 14 章专门讲 enum 的四种 tag 策略。本章 + 第 14 章合起来覆盖了反序列化所有主要设计点。
13.2 expand_derive_deserialize 的骨架
先看入口(serde_derive/src/de.rs:25,精简):
rust
pub fn expand_derive_deserialize(input: &mut syn::DeriveInput) -> syn::Result<TokenStream> {
replace_receiver(input);
let ctxt = Ctxt::new();
let Some(cont) = Container::from_ast(&ctxt, input, Derive::Deserialize, &private.ident()) else {
return Err(ctxt.check().unwrap_err());
};
precondition(&ctxt, &cont);
ctxt.check()?;
let ident = &cont.ident;
let params = Parameters::new(&cont);
let (de_impl_generics, de_ty_generics, ty_generics, where_clause) = split_with_de_lifetime(¶ms);
let body = Stmts(deserialize_body(&cont, ¶ms));
let in_place_body = deserialize_in_place_body(&cont, ¶ms);
let in_place_impl_generics = ...;
let impl_block = if let Some(remote) = cont.attrs.remote() {
// remote 模式
quote! {
impl #de_impl_generics #ident #ty_generics #where_clause {
pub fn deserialize<__D>(__deserializer: __D)
-> _serde::#private::Result<#remote #ty_generics, __D::Error>
where __D: _serde::Deserializer<'de>,
{
#body
}
}
}
} else {
// 标准 impl Deserialize
quote! {
#[automatically_derived]
impl #de_impl_generics _serde::Deserialize<'de> for #ident #ty_generics #where_clause {
fn deserialize<__D>(__deserializer: __D)
-> _serde::#private::Result<Self, __D::Error>
where __D: _serde::Deserializer<'de>,
{
#body
}
#in_place_body
}
}
};
Ok(dummy::wrap_in_const(cont.attrs.custom_serde_path(), impl_block))
}和 Serialize 入口的相似之处:parse → Container → precondition → 主分派 → impl 外壳 → dummy 包装。
不同之处:
split_with_de_lifetime:返回四个 generics 片段,多出了一个de_impl_generics(它的<>里包含'de生命周期)——因为impl<'de> Deserialize<'de> for T。deserialize_in_place_body:除了deserialize还可能实现deserialize_in_place——Deserialize trait 的一个进阶方法(第 4 章的deserialize_in_place文档说过)。
split_with_de_lifetime 是一个值得记住的函数。它在原 generics 上加 'de 和 T: Deserialize<'de> bound,生成:
rust
// 原:struct User<T>
// 生成:impl<'de, T: Deserialize<'de>> Deserialize<'de> for User<T>
// ^^^ ^^^^^^^^^^^^^^^^^^
// de_impl_generics bound这种生命周期推导是第 15 章"借用反序列化"的前置知识。
13.3 deserialize_body:五路分派
主分派(serde_derive/src/de.rs:306):
rust
fn deserialize_body(cont: &Container, params: &Parameters) -> Fragment {
if cont.attrs.transparent() {
deserialize_transparent(cont, params)
} else if let Some(type_from) = cont.attrs.type_from() {
deserialize_from(type_from)
} else if let Some(type_try_from) = cont.attrs.type_try_from() {
deserialize_try_from(type_try_from)
} else {
match &cont.data {
Data::Enum(variants) => deserialize_enum(params, variants, &cont.attrs),
Data::Struct(Style::Struct, fields) => deserialize_struct(params, fields, &cont.attrs, StructForm::Struct),
Data::Struct(Style::Tuple, fields) => deserialize_tuple(params, fields, &cont.attrs, TupleForm::Tuple),
Data::Struct(Style::Newtype, fields) => deserialize_newtype_struct(params, &fields[0], &cont.attrs),
Data::Struct(Style::Unit, _) => deserialize_unit_struct(params, &cont.attrs),
}
}
}和 Serialize 对称的五路分派:transparent / from / try_from / struct 四种 style / enum。
注意 type_from 和 type_try_from——Serialize 对应的是 type_into,这是反序列化时"先反序列化为 X 类型,再 convert 成 Self" 的机制。它们是 remote 类型常用的 workaround 之一。
13.4 deserialize_struct:Visitor 模式的落地
现在看重头戏——普通 struct 的反序列化生成。以这个用户代码为例:
rust
#[derive(Deserialize)]
struct User {
id: u64,
name: String,
}反序列化逻辑(不涉及格式细节):
- 告诉 Deserializer:"我期待一个 struct,名字 User,字段 [id, name]"
- Deserializer 解析输入,根据输入形态调用 Visitor 的 visit_seq(如果是顺序)或 visit_map(如果是键值对)
- Visitor 在 visit_map 里循环读取 key-value 对,识别 key 是哪个字段,把 value 存起来
- 所有字段读完后,组装 User 并返回
生成代码(cargo expand 观察,精简):
rust
impl<'de> Deserialize<'de> for User {
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,
{
// === 第一部分:字段标识符枚举 ===
enum __Field {
__field0, // 代表 id
__field1, // 代表 name
__ignore, // 未知字段
}
struct __FieldVisitor;
impl<'de> Visitor<'de> for __FieldVisitor {
type Value = __Field;
fn expecting(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("field identifier")
}
fn visit_u64<__E: de::Error>(self, v: u64) -> Result<__Field, __E> {
match v {
0u64 => Ok(__Field::__field0),
1u64 => Ok(__Field::__field1),
_ => Ok(__Field::__ignore),
}
}
fn visit_str<__E: de::Error>(self, v: &str) -> Result<__Field, __E> {
match v {
"id" => Ok(__Field::__field0),
"name" => Ok(__Field::__field1),
_ => Ok(__Field::__ignore),
}
}
fn visit_bytes<__E: de::Error>(self, v: &[u8]) -> Result<__Field, __E> {
match v {
b"id" => Ok(__Field::__field0),
b"name" => Ok(__Field::__field1),
_ => Ok(__Field::__ignore),
}
}
}
impl<'de> Deserialize<'de> for __Field {
fn deserialize<__D: Deserializer<'de>>(d: __D) -> Result<Self, __D::Error> {
d.deserialize_identifier(__FieldVisitor)
}
}
// === 第二部分:主 Visitor ===
struct __Visitor<'de> { marker: PhantomData<User>, lifetime: PhantomData<&'de ()> }
impl<'de> Visitor<'de> for __Visitor<'de> {
type Value = User;
fn expecting(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("struct User")
}
// 顺序模式:从 seq 读取
fn visit_seq<__A: SeqAccess<'de>>(self, mut __seq: __A) -> Result<User, __A::Error> {
let __field0 = match __seq.next_element::<u64>()? {
Some(v) => v,
None => return Err(de::Error::invalid_length(0, &"struct User with 2 elements")),
};
let __field1 = match __seq.next_element::<String>()? {
Some(v) => v,
None => return Err(de::Error::invalid_length(1, &"struct User with 2 elements")),
};
Ok(User { id: __field0, name: __field1 })
}
// 映射模式:从 map 读取
fn visit_map<__A: MapAccess<'de>>(self, mut __map: __A) -> Result<User, __A::Error> {
let mut __field0: Option<u64> = None;
let mut __field1: Option<String> = None;
while let Some(__key) = __map.next_key::<__Field>()? {
match __key {
__Field::__field0 => {
if __field0.is_some() {
return Err(de::Error::duplicate_field("id"));
}
__field0 = Some(__map.next_value::<u64>()?);
}
__Field::__field1 => {
if __field1.is_some() {
return Err(de::Error::duplicate_field("name"));
}
__field1 = Some(__map.next_value::<String>()?);
}
__Field::__ignore => {
__map.next_value::<de::IgnoredAny>()?;
}
}
}
let __field0 = __field0.ok_or_else(|| de::Error::missing_field("id"))?;
let __field1 = __field1.ok_or_else(|| de::Error::missing_field("name"))?;
Ok(User { id: __field0, name: __field1 })
}
}
// === 第三部分:入口调用 ===
const FIELDS: &[&str] = &["id", "name"];
__deserializer.deserialize_struct("User", FIELDS, __Visitor { marker: PhantomData, lifetime: PhantomData })
}
}这是一个 80+ 行的生成代码——从 10 行用户定义生成出来。让我们拆开看它的三部分。
13.5 第一部分:字段标识符枚举 (identifier.rs)
生成代码的第一部分定义了 __Field enum 和 __FieldVisitor——它们负责识别输入里的字段名。
为什么要这个 enum? 因为 Deserializer 对 next_key 的返回类型是泛型 T: Deserialize。Serde 需要一个"key 类型"告诉 Deserializer"我期待一个标识符,它可能是字符串(JSON)或整数(Bincode 里字段按序号编码)"。
__FieldVisitor 的三个 visit 方法:
visit_u64: 处理字段索引。Bincode 等紧凑格式不写字段名,按顺序给出字段索引。生成代码里0u64 → __field0、1u64 → __field1。visit_str: 处理字段字符串名。JSON、YAML 等格式用字符串 key。visit_bytes: 处理字段的字节字符串名。某些格式可能用字节形式传递 key(如自定义 binary 格式)。
__ignore 变体的角色:未知字段。默认行为是忽略(以支持向前兼容)。如果用户写了 #[serde(deny_unknown_fields)],这个 variant 会被替换成返回错误。
这段代码的生成逻辑在 serde_derive/src/de/identifier.rs(477 行)。它是独立文件,因为:
- 字段标识符的 Visitor 复杂(要处理 u64/str/bytes 三种形态)。
- Enum 的变体识别也走同一套逻辑——复用 identifier.rs。
#[serde(alias = "...")]支持,每个字段可以有多个别名。
13.6 第二部分:主 Visitor 和 visit_seq / visit_map
主 Visitor __Visitor 有两个 visit_* 方法:
visit_seq:处理"序列"形式输入(如果格式把 struct 编码成顺序数组)。
生成模式:
rust
let __field0 = seq.next_element::<FIELD0_TYPE>()?
.ok_or_else(|| invalid_length(...))?;
let __field1 = seq.next_element::<FIELD1_TYPE>()?
.ok_or_else(|| invalid_length(...))?;
// ...
Ok(Self { field0: __field0, field1: __field1, ... })每个字段调一次 next_element。如果返回 None(元素不够)报 invalid_length 错。
visit_map:处理"键值映射"形式(JSON 对象的典型情况)。
生成模式:
rust
let mut __field0: Option<T0> = None;
let mut __field1: Option<T1> = None;
// ...
while let Some(key) = map.next_key::<__Field>()? {
match key {
__Field::__field0 => {
if __field0.is_some() { return Err(duplicate_field("name0")); }
__field0 = Some(map.next_value::<T0>()?);
}
// ...
__Field::__ignore => { map.next_value::<IgnoredAny>()?; }
}
}
let __field0 = __field0.ok_or_else(|| missing_field("name0"))?;
// ...
Ok(Self { ... })几个关键设计点:
1. 为什么用 Option<T> 累积? 因为字段顺序不确定、字段可能缺失。Option::None 表示"还没收到"。最后一步的 .ok_or_else(|| missing_field("..."))? 检查必要字段是否都有。
2. 重复字段检测:if __field0.is_some() { Err(duplicate_field(...)) }——JSON 规范说"重复 key 行为未定义",但 serde 明确报错。这是 serde 的严格策略。
3. IgnoredAny:对未知字段,调用 map.next_value::<IgnoredAny>() 消耗它的 value(不管是什么格式)。IgnoredAny 是一个特殊类型,它的 Deserialize 对任何输入都成功并丢弃。
13.7 第三部分:入口调用
最后一段:
rust
const FIELDS: &[&str] = &["id", "name"];
__deserializer.deserialize_struct("User", FIELDS, __Visitor { marker: PhantomData, lifetime: PhantomData })告诉 Deserializer:"我期待一个名为 User 的 struct、字段列表是 FIELDS"。Deserializer 根据输入形态决定调 visit_seq 还是 visit_map。
为什么要传 FIELDS 常量? 因为某些 Deserializer 实现可以用这个列表做优化——比如 JSON deserializer 可以在解析 key 时用 FIELDS 做快速对比,而不是每次都构造 String。
PhantomData 的两个字段:
marker: PhantomData<User>:让编译器把__Visitor<'de>和User类型关联起来——需要时推导出"Visitor 的 Value 是 User"。lifetime: PhantomData<&'de ()>:让'de生命周期在__Visitor里 "有意义"——即使 Visitor 内部当前没有借用'de数据,这个 PhantomData 声明了"如果将来有借用字段,生命周期是'de"。
13.8 de/struct_.rs:697 行的细节
上面例子是简化版。真实的生成代码在 serde_derive/src/de/struct_.rs 里生成,5 个顶层函数组织这 697 行:
| 行号 | 函数 | 职责 |
|---|---|---|
| 17 | pub(super) fn deserialize | struct 反序列化的入口——生成 impl Deserialize<'de> |
| 199 | fn deserialize_map | 生成 visit_map 方法体——即 13.6 节讲的核心循环 |
| 423 | pub(super) fn deserialize_in_place | 生成 deserialize_in_place 方法(见 13.11 节) |
| 513 | fn deserialize_map_in_place | in_place 版本的 visit_map |
| 673 | fn deserialize_field_identifier | 生成 __Field + __FieldVisitor(13.5 节) |
每个函数约 100-300 行。deserialize_map(199-421 行)是单个最大的生成函数——它按属性(default、flatten、deny_unknown_fields、borrow 等)分成十几个 if/match 分支,每种组合有对应的 quote! 模板。这里要处理的属性组合:
#[serde(default)]和#[serde(default = "fn")]:字段缺失时用默认值#[serde(skip_deserializing)]:始终不反序列化此字段(用默认值)#[serde(flatten)]:把字段的 struct "摊平" 到父级#[serde(borrow)]:让字段借用'de数据#[serde(deserialize_with = "fn")]:自定义反序列化函数
每种属性都改变生成代码的某一部分。例如 #[serde(default = "my_fn")]:
rust
// 原生成(无 default)
let __field0 = __field0.ok_or_else(|| missing_field("name0"))?;
// 带 default = "my_fn"
let __field0 = __field0.unwrap_or_else(my_fn);#[serde(flatten)] 最特殊——它不能走普通字段枚举逻辑(因为 flatten 字段吸纳所有未知 key)。生成代码会完全换一种 visit_map 模式:收集所有未知字段到一个 Map,然后调用 flatten 字段的 Deserialize::deserialize 从这个 Map 解析。
#[serde(borrow)] 影响字段类型的生命周期约束(第 15 章详述)。
697 行的 struct_.rs 处理所有这些分支。每一行都是一个具体属性场景的代码生成逻辑。本章不展开每一条——读懂上面的基础模式,你就能在需要时按图索骥找到具体属性的处理位置。
13.9 tuple struct 和 unit struct 的反序列化
tuple struct(de/tuple.rs,283 行):
rust
#[derive(Deserialize)]
struct Point(i32, i32);生成的 Visitor 只实现 visit_seq——tuple struct 不走 map 路径(没有字段名):
rust
impl<'de> Visitor<'de> for __Visitor<'de> {
type Value = Point;
fn expecting(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("tuple struct Point")
}
fn visit_seq<__A: SeqAccess<'de>>(self, mut seq: __A) -> Result<Point, __A::Error> {
let __field0 = seq.next_element::<i32>()?
.ok_or_else(|| invalid_length(0, &"tuple struct Point with 2 elements"))?;
let __field1 = seq.next_element::<i32>()?
.ok_or_else(|| invalid_length(1, &"tuple struct Point with 2 elements"))?;
Ok(Point(__field0, __field1))
}
}
__deserializer.deserialize_tuple_struct("Point", 2, __Visitor { ... })简单得多——只有一个 visit 方法。
unit struct(de/unit.rs,52 行):
rust
#[derive(Deserialize)]
struct Empty;生成代码更简单:
rust
struct __Visitor;
impl<'de> Visitor<'de> for __Visitor {
type Value = Empty;
fn expecting(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("unit struct Empty")
}
fn visit_unit<__E: de::Error>(self) -> Result<Empty, __E> {
Ok(Empty)
}
}
__deserializer.deserialize_unit_struct("Empty", __Visitor)只实现 visit_unit——单元结构没有数据。52 行代码是因为要处理一些边缘情况,不是核心逻辑复杂。
13.10 默认值与 skip_deserializing
#[serde(default = "my_fn")] 是最常见的"字段可以缺失"场景。
生成代码(对照看差异):
rust
// 无 default
let __field0 = __field0.ok_or_else(|| missing_field("id"))?;
// #[serde(default)]
let __field0 = __field0.unwrap_or_else(Default::default);
// #[serde(default = "my_fn")]
let __field0 = __field0.unwrap_or_else(my_fn);
// #[serde(skip_deserializing)]
// 字段完全不参与 visit_map 的 match,直接用默认值
let __field0 = Default::default(); // 或 my_fn() 如果也有 default默认值的处理分两阶段:
- 读取阶段:visit_map 里
__field0: Option<T> = None,遇到字段则Some(...)。 - 组装阶段:
let __field0 = match __field0 { Some(v) => v, None => my_fn() }。
#[serde(skip_deserializing)] 跳过读取阶段,直接进组装——反正从来不会被读到。
如果 整个 struct 有 #[serde(default)]:一个特殊优化——不用 Option,直接初始化成默认值:
rust
let mut __result = User::default(); // struct-level default
while let Some(key) = __map.next_key::<__Field>()? {
match key {
__Field::__field0 => { __result.id = __map.next_value()?; }
__Field::__field1 => { __result.name = __map.next_value()?; }
__Field::__ignore => { __map.next_value::<IgnoredAny>()?; }
}
}
Ok(__result)更紧凑、性能更好(不用 Option wrap)、也不会报 missing_field 错误。
13.11 deserialize_in_place:高级复用优化
除了标准 deserialize 方法,Serde 还提供一个可选方法 deserialize_in_place:
rust
// serde_core/src/de/mod.rs:585
fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
where D: Deserializer<'de>,
{
*place = Deserialize::deserialize(deserializer)?;
Ok(())
}默认实现 就是"调 deserialize 然后赋值给 place"。override 的价值是可以复用 place 里已有的内存——比如反序列化 JSON 数组到一个已有的 Vec<T>,可以复用 Vec 的 capacity 和部分 items,而不是重新分配。
serde_derive 在 struct 上生成这个方法:
rust
fn deserialize_in_place<__D: Deserializer<'de>>(d: __D, place: &mut Self) -> Result<(), __D::Error> {
// 类似 deserialize,但在 visit_seq/visit_map 里直接写到 place.field
...
}默认关闭——只在类型有优化空间时生成。因为大多数类型(原子类型、简单 struct)默认实现就够了。
13.11.1 deserialize_in_place 在 serde_derive 里的精确 opt-out 条件
"默认关闭"这句话掩盖了两层真相——打开 serde_derive/src/de.rs:332:
rust
#[cfg(feature = "deserialize_in_place")] // ← 外层 cargo feature 开关
fn deserialize_in_place_body(cont, params) -> Option<Stmts> {
assert!(!params.has_getter); // ← 防御性断言
if cont.attrs.transparent() // ← 四种 opt-out 之一
|| cont.attrs.type_from().is_some() // ← opt-out 二
|| cont.attrs.type_try_from().is_some() // ← opt-out 三
|| cont.attrs.identifier().is_some() // ← opt-out 四
|| cont.data.all_fields()
.all(|f| f.attrs.deserialize_with().is_some()) // ← opt-out 五
{
return None;
}
let code = match &cont.data {
Data::Struct(Style::Struct, fields) =>
struct_::deserialize_in_place(params, fields, &cont.attrs)?,
Data::Struct(Style::Tuple, fields) | Data::Struct(Style::Newtype, fields) =>
tuple::deserialize_in_place(params, fields, &cont.attrs),
Data::Enum(_) | Data::Struct(Style::Unit, _) => return None, // ← opt-out 六
};
// ...
}
#[cfg(not(feature = "deserialize_in_place"))] // ← feature 不开时的 stub
fn deserialize_in_place_body(_cont, _params) -> Option<Stmts> {
None
}两层 gate 组合:
外层——#[cfg(feature = "deserialize_in_place")]:整个 deserialize_in_place 是 serde_derive 的可选 cargo feature,默认不开启。用户要在 Cargo.toml 里显式写:
toml
[dependencies]
serde_derive = { version = "1", features = ["deserialize_in_place"] }才会有任何生成代码。这不是"按类型决定"——是"整个项目决定"。原因在于:大部分 Rust 项目不需要这个优化(reuse 内存省的那点分配时间对 LLM 应用、Web Server 等都可以忽略),为它多生成代码会拖慢 cargo check。serde_derive 选择默认不收这个代价、想要的用户自己加 feature。
内层六条 opt-out 规则(即便 feature 开了也可能跳过):
#[serde(transparent)]——透明包装只转发给内部字段、没必要额外 in-place 代码#[serde(from = "...")]——通过From::from转换的类型、in-place 无意义#[serde(try_from = "...")]——TryFrom转换、同上#[serde(identifier)]——枚举被当 identifier 用、不是数据容器- 所有字段都有
#[serde(deserialize_with = "...")]——用户每个字段都接管了反序列化、serde_derive 没插话空间 enum或unit struct——枚举的结构每次变体都不同、没稳定 "place" 可更新;unit struct 没字段
每条都对应一个"in-place 在这类情形下不可能工作或毫无价值"的明确论证。
assert!(!params.has_getter) 防御性断言(line 336):remote derives 用 getter 方法访问字段、不能表达 "直接写进 &mut self.field" 的 in-place 语义。前面的控制流已经保证 has_getter 不会走到这里——assert 捕获未来重构时的 rustc bug、在 serde_derive 自己崩之前就报错。这是 "为自己的未来重构留保险丝" 的典型手法——和第 2 章 rustc debug_assert_ne! 同源。
结构体 vs 元组的分叉实现(line 350-359):
Style::Struct→struct_::deserialize_in_place(按名字匹配字段、需要 fallback 处理未知字段)Style::Tuple/Style::Newtype→tuple::deserialize_in_place(按位置匹配、更简单)Enum/Unit→None
两个分工明确的子模块——而不是一个"通用 in-place"函数。原因是 struct 的 field lookup 用 visit_map、tuple 的用 visit_seq——Visitor 回调签名完全不同、代码重用没多少收益、拆两份更清晰。这和第 10 章讲的 bound.rs 只有一个通用函数形成对比——bound 分析对所有结构统一;代码生成则是结构特异的。
这一整段 50 行的 deserialize_in_place_body 是一个 "外层 feature gate + 内层语义 opt-out + 防御性 assert + 结构分叉" 的四层过滤器。如果你看到生成的代码里有 deserialize_in_place、意味着四层都通过——是个非常精挑细选的子集。
13.12 Deserialize 的 bound 推导
和 Serialize 一样,Deserialize 也要为泛型类型自动推导 bound:
rust
#[derive(Deserialize)]
struct Wrapper<T> { data: T }
// 生成:
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Wrapper<T> { ... }bound.rs(425 行,第 10 章略讲)同时为 Serialize 和 Deserialize 工作。它遍历字段类型,对泛型参数加 T: Deserialize<'de> 约束(或 Serialize 约束)。
一个特别的 deserialize bound 问题:
rust
struct Wrapper<T> {
data: PhantomData<T>, // 这个 T 并不真的参与反序列化
}如果简单推导 T: Deserialize<'de>,用户在 PhantomData<T> 里放不能反序列化的 T 时会失败。所以 serde 特殊识别 PhantomData<T>——不为 PhantomData 里的 T 加 bound。
这类特殊情况在 bound.rs 里有完整清单。你读这个文件会看到许多 if let Type::Path(path) = &field.ty { if segment.ident == "PhantomData" ...——每一处都是一个 serde 学过的教训。
13.13 真实例子:完整 cargo expand 观察
把前面的内容整合,看真实的生成代码。用户:
rust
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct User {
id: u64,
#[serde(default = "default_name")]
name: String,
#[serde(skip_deserializing)]
internal: Vec<u8>,
}
fn default_name() -> String { "anonymous".to_string() }cargo expand 真实输出(serde 1.0.228 + serde_derive 1.0.228,2026-04-20 实测,已精简重复结构但语义 1:1 保留):
rust
#[automatically_derived]
impl<'de> _serde::Deserialize<'de> for UserStrict {
fn deserialize<__D>(
__deserializer: __D,
) -> _serde::__private228::Result<Self, __D::Error>
where
__D: _serde::Deserializer<'de>,
{
#[allow(non_camel_case_types)]
#[doc(hidden)]
enum __Field {
__field0,
__field1,
// 注意:没有 __ignore——deny_unknown_fields 关闭了它
// 注意:internal 字段(skip_deserializing)不在 __Field 中
}
#[doc(hidden)]
struct __FieldVisitor;
impl<'de> _serde::de::Visitor<'de> for __FieldVisitor {
type Value = __Field;
fn expecting(
&self,
__formatter: &mut _serde::__private228::Formatter,
) -> _serde::__private228::fmt::Result {
_serde::__private228::Formatter::write_str(__formatter, "field identifier")
}
fn visit_u64<__E>(self, __value: u64) -> _serde::__private228::Result<Self::Value, __E>
where __E: _serde::de::Error,
{
match __value {
0u64 => _serde::__private228::Ok(__Field::__field0),
1u64 => _serde::__private228::Ok(__Field::__field1),
_ => _serde::__private228::Err(_serde::de::Error::invalid_value(
_serde::de::Unexpected::Unsigned(__value),
&"field index 0 <= i < 2",
)),
}
}
fn visit_str<__E>(self, __value: &str) -> _serde::__private228::Result<Self::Value, __E>
where __E: _serde::de::Error,
{
match __value {
"id" => _serde::__private228::Ok(__Field::__field0),
"name" => _serde::__private228::Ok(__Field::__field1),
_ => _serde::__private228::Err(
_serde::de::Error::unknown_field(__value, FIELDS),
),
}
}
fn visit_bytes<__E>(self, __value: &[u8]) -> _serde::__private228::Result<Self::Value, __E>
where __E: _serde::de::Error,
{ /* 类似 visit_str,对字节形式 key */ }
}
impl<'de> _serde::Deserialize<'de> for __Field {
#[inline]
fn deserialize<__D>(__deserializer: __D) -> _serde::__private228::Result<Self, __D::Error>
where __D: _serde::Deserializer<'de>,
{
_serde::Deserializer::deserialize_identifier(__deserializer, __FieldVisitor)
}
}
#[doc(hidden)]
struct __Visitor<'de> {
marker: _serde::__private228::PhantomData<UserStrict>,
lifetime: _serde::__private228::PhantomData<&'de ()>,
}
impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> {
type Value = UserStrict;
fn expecting(&self, __formatter: &mut _serde::__private228::Formatter) -> _serde::__private228::fmt::Result {
_serde::__private228::Formatter::write_str(__formatter, "struct UserStrict")
}
#[inline]
fn visit_seq<__A>(self, mut __seq: __A) -> _serde::__private228::Result<Self::Value, __A::Error>
where __A: _serde::de::SeqAccess<'de>,
{
let __field0 = match _serde::de::SeqAccess::next_element::<u64>(&mut __seq)? {
_serde::__private228::Some(__value) => __value,
_serde::__private228::None => return _serde::__private228::Err(
_serde::de::Error::invalid_length(0usize, &"struct UserStrict with 2 elements"),
),
};
let __field1 = match _serde::de::SeqAccess::next_element::<String>(&mut __seq)? {
_serde::__private228::Some(__value) => __value,
_serde::__private228::None => default_name(), // default = "default_name"
};
let __field2 = _serde::__private228::Default::default(); // internal 字段
_serde::__private228::Ok(UserStrict {
id: __field0,
name: __field1,
internal: __field2,
})
}
#[inline]
fn visit_map<__A>(self, mut __map: __A) -> _serde::__private228::Result<Self::Value, __A::Error>
where __A: _serde::de::MapAccess<'de>,
{
let mut __field0: _serde::__private228::Option<u64> = _serde::__private228::None;
let mut __field1: _serde::__private228::Option<String> = _serde::__private228::None;
while let _serde::__private228::Some(__key) =
_serde::de::MapAccess::next_key::<__Field>(&mut __map)?
{
match __key {
__Field::__field0 => {
if _serde::__private228::Option::is_some(&__field0) {
return _serde::__private228::Err(
<__A::Error as _serde::de::Error>::duplicate_field("id"),
);
}
__field0 = _serde::__private228::Some(
_serde::de::MapAccess::next_value::<u64>(&mut __map)?,
);
}
__Field::__field1 => {
if _serde::__private228::Option::is_some(&__field1) {
return _serde::__private228::Err(
<__A::Error as _serde::de::Error>::duplicate_field("name"),
);
}
__field1 = _serde::__private228::Some(
_serde::de::MapAccess::next_value::<String>(&mut __map)?,
);
}
}
}
let __field0 = match __field0 {
_serde::__private228::Some(__field0) => __field0,
_serde::__private228::None => _serde::__private228::de::missing_field("id")?,
};
let __field1 = match __field1 {
_serde::__private228::Some(__field1) => __field1,
_serde::__private228::None => default_name(), // default = "default_name"
};
_serde::__private228::Ok(UserStrict {
id: __field0,
name: __field1,
internal: _serde::__private228::Default::default(), // skip_deserializing 字段
})
}
}
#[doc(hidden)]
const FIELDS: &'static [&'static str] = &["id", "name"]; // 不含 internal
_serde::Deserializer::deserialize_struct(
__deserializer, "UserStrict", FIELDS,
__Visitor {
marker: _serde::__private228::PhantomData::<UserStrict>,
lifetime: _serde::__private228::PhantomData,
},
)
}
}观察点(对照前 13.4 节的基础版,精确标记三处属性的影响):
deny_unknown_fields改变了 visit_str:基础版是_ => Ok(__Field::__ignore),这里变成_ => Err(Error::unknown_field(__value, FIELDS))。__Fieldenum 也少了__ignore变体。注意visit_u64对"超出范围的 index" 返回invalid_value(不是unknown_field)——因为数字索引越界是"值无效",字符串未知是"字段未知",两种错误语义不同。default = "default_name":组装阶段的 match 变成None => default_name(),替代基础版的missing_field错误。注意这里 不是unwrap_or_else(default_name)而是展开的 match 形式——更直观的错误位置管理。skip_deserializing:internal字段在__Field里不存在、visit_map 里没有__field2局部变量、FIELDS常量里不包含 "internal"——彻底不参与反序列化。组装时直接_serde::__private228::Default::default()填值。visit_seq也处理 default:注意visit_seq里__field1为 None 时也调default_name(),和 visit_map 一致——default 跨两种输入形态统一工作。#[inline]注释:visit_seq、visit_map、__Field::deserialize都带#[inline]——serde_derive 给这些关键方法加内联提示,让 rustc 能更激进优化。
三个小改动对应三个属性——每个属性精确修改生成代码的一处。这就是第 11 章属性系统 → 第 13 章代码生成的完整流水线。
13.14 serde_derive/src/de/ 9 文件 2383 行的真实拆分
ch12 里只提到 Deserialize 顶层入口在 de.rs(976 行)——实际上大头都在 serde_derive/src/de/ 子目录的 9 个文件里、总计 2383 行——
| 文件 | 行 | 角色 |
|---|---|---|
struct_.rs | 697 | 本章 §13.4-13.8 的主角——structuret + __Field + __Visitor 生成的近千行 quote! 模板 |
identifier.rs | 477 | 字段/变体标识符 visitor——跨 struct / enum 共用——为什么 §14 enum 和本章 struct 都依赖它 |
enum_adjacently.rs | 324 | adjacently tagged enum({"tag": "X", "content": {...}})——最复杂的 tag 模式、独占整个文件 |
tuple.rs | 283 | tuple struct 的 visit_seq 生成 |
enum_externally.rs | 213 | externally tagged enum(默认)—— {"X": {...}} |
enum_untagged.rs | 135 | untagged enum——按变体顺序试错回溯 |
enum_internally.rs | 106 | internally tagged({"type": "X", ...} 扁平) |
enum_.rs | 96 | enum 的入口分派——按 cattrs.tag() 分到 4 种 tag 模式 |
unit.rs | 52 | unit struct(最简单) |
加上 de.rs 本身的 976 行顶层代码、整个 Deserialize 生成器 3359 行——与 Serialize(ser.rs 单文件 1369 行)的 3.06 倍体量差距——根本原因是本章 §13.1 讲过的"Visitor 模式要求每种输入形态各写一个 visit_xxx 方法"。
四个 tag 模式文件的大小梯度——adjacently (324) > externally (213) > untagged (135) > internally (106)——adjacently 比最简单的 internally 复杂 3 倍——因为 adjacently 既要识别 tag 字段又要识别 content 字段、还要处理两者顺序任意(tag 先出 / content 先出)+ 嵌套泛型——工程代价最大。
一个容易被忽视的事实——identifier.rs 477 行是全 de/ 第二大——因为 struct 字段识别和 enum 变体识别本质是同一件事(都是"从输入的 string / u64 / bytes 识别成一个枚举 variant")——serde_derive 把这段逻辑抽取成单文件共用、是本目录设计纪律的体现。
跨书索引——mcp/chapters/03-jsonrpc-message.md 用的所有 #[derive(Deserialize)] 都是本章讲的这个生成器产出——读过本章再看那里的代码能指出每一处为什么是那样。
13.14.1 缺字段、借用和 in-place:三个小分支决定生成代码的正确性
本章主要看生成代码长什么样,这里补一层源码决策:serde_derive 在生成 Deserialize 时,最容易出错的不是 visitor 外壳,而是三个边界分支。
第一,泛型 bound 必须只加给真正由 Serde 反序列化的字段。 serde_derive/src/de.rs:230-245 的注释和 needs_deserialize_bound 函数直接写明:带 skip_deserializing、deserialize_with、字段级 bound,或者变体级 skip_deserializing / deserialize_with / bound 的字段,不会自动生成 T: Deserialize<'de>。原因是这些字段要么根本不从输入读,要么交给用户函数读,要么用户已经给了自定义 bound。
如果宏不做这个过滤,下面这个类型会被误伤:
rust
#[derive(Deserialize)]
struct Job<T> {
id: u64,
#[serde(skip_deserializing)]
marker: std::marker::PhantomData<T>,
}T 没有必要实现 Deserialize<'de>,因为 marker 不从输入里来。第 10 章讲过 bound 推导是 Serde "魔法感" 的来源;这里能看到魔法的边界同样重要。
第二,借用生命周期只从参与反序列化的字段收集。 serde_derive/src/de.rs:292-303 的 borrowed_lifetimes 遍历所有字段,但在 de.rs:295-296 明确跳过 skip_deserializing 字段。这样 #[serde(borrow)] 不会因为一个被跳过字段而污染整个 impl 的 'de 约束。第 15 章会详细讲 &'de str,但在生成器里它已经体现为一个集合运算:哪些字段真的借用输入,哪些字段只是类型上带生命周期。
第三,缺字段不是统一 Default::default()。 serde_derive/src/de.rs:763-803 的 expr_is_missing 把缺字段分成四类:
| 情况 | 生成行为 | 源码依据 |
|---|---|---|
字段 #[serde(default)] | 调 _serde::__private::Default::default() | de.rs:764-769 |
字段 #[serde(default = "path")] | 调用户路径 path() | de.rs:770-775 |
容器 #[serde(default)] | 从 __default.field 取值 | de.rs:780-784 |
| 没有 default | 调 missing_field(name)? 或返回 missing_field 错误 | de.rs:788-800 |
这张表解释了为什么 cargo expand 里缺字段处理看起来啰嗦。宏不能简单写 unwrap_or_default(),因为字段 default、容器 default、自定义函数、deserialize_with 的错误位置都不同。尤其 de.rs:771-775 的注释说明:如果 default = "path" 返回类型不对,错误要指向属性里的 path,而不是宏生成代码的某个临时变量。
in-place 反序列化也不是所有类型都支持。 在启用 deserialize_in_place feature 时,serde_derive/src/de.rs:332-360 会尝试生成 deserialize_in_place_body,但 de.rs:338-347 先排除 transparent、from、try_from、identifier,以及所有字段都由 deserialize_with 处理的情况;de.rs:350-359 又只允许 struct 和 tuple/newtype,enum 与 unit struct 直接返回 None。这说明 in-place 不是"把所有字段原地覆盖"这么简单,它要求生成器能明确知道每个字段如何被更新。
再把这些分支接回本章的 struct visitor:serde_derive/src/de/struct_.rs:56-65 会先把可反序列化且非 flatten 的字段收集成 FieldWithAliases;struct_.rs:122-130 再生成 FIELDS 常量;struct_.rs:139-147 对普通 struct 调 Deserializer::deserialize_struct。字段名识别则交给 serde_derive/src/de/identifier.rs:185-224,它为每个 alias 生成 string/bytes 两套路由。
因此,Deserialize 生成器不是"为每个字段写一个 match"。它要同时维护五个集合:
| 集合 | 包含什么 | 影响哪里 |
|---|---|---|
| 可反序列化字段 | 未 skip、未 flatten 的字段 | __Field、FIELDS、visit_map |
| alias 集合 | 字段主名 + alias | __FieldVisitor 的匹配分支 |
| borrowed lifetime 集合 | 真正借用输入的字段生命周期 | impl generics 的 'de: 'a |
| default 字段集合 | 字段或容器提供默认值 | 缺字段组装逻辑 |
| 用户接管字段 | deserialize_with / 显式 bound | bound 推导和错误路径 |
这五个集合任何一个算错,生成代码都可能出现"编译不过"或"运行时语义不对"。Serde 的成熟之处就在这里:它把这些集合在源码里明确分层,而不是把判断散落在 quote 模板字符串里。
再看 alias 的路径会更具体。serde_derive/src/de/identifier.rs:62-68 把每个 variant 或字段整理成 FieldWithAliases { ident, aliases },而 identifier.rs:194-217 同时生成字符串匹配和字节串匹配。这样 #[serde(alias = "...")] 不需要改主 visit_map 逻辑,只是扩展字段识别器的入口集合。对 JSON 来说字段名通常是字符串;对其他格式来说,identifier 也可能以 bytes 或整数索引形态出现。Serde 把"识别字段名"单独抽出,正是为了让 struct、enum variant、alias、deny_unknown_fields 能共享一套机制。
serde_derive/src/de/struct_.rs:70-95 还展示了另一个生成策略:不是所有 struct 都生成 visit_seq。untagged struct variant 和含 flatten 的 struct 会跳过 seq 路径,因为它们必须从 map/content 形态里识别字段。struct_.rs:139-147 对普通 struct 调 deserialize_struct,但 struct_.rs:140-142 对 flatten struct 改调 deserialize_map。这说明一个 #[serde(flatten)] 不是"多收几个字段"那么简单,它会改变 Deserializer 入口,进而改变可接受的输入形态。
把这些边界放在一起,Deserialize 生成器的复杂度就不再神秘:
- 字段识别独立出来,解决 rename、alias、unknown field。
- 字段存储用
Option<T>,解决重复字段和缺字段。 - 组装表达式独立出来,解决 default、skip、deserialize_with。
- 入口分派按 struct/tuple/enum/flatten 选择,解决输入形态差异。
- 泛型和生命周期在外层集中计算,解决 impl 头部正确性。
这五步每一步都可以在源码里定位。读 cargo expand 时看到一大段 __FieldVisitor、__Visitor、FIELDS 常量,不要把它当宏噪音;它们分别对应这五步中的前三步。真正的工程理解,是能从展开代码反推回生成器为什么要有这些集合和分支。
如果你在项目里调试一个奇怪的 Deserialize 行为,可以按这个顺序缩小范围:字段名不匹配,看 __FieldVisitor;重复字段或缺字段,看 visit_map 里的 Option 存储;默认值不符合预期,看 expr_is_missing 对应的分支;flatten 行为异常,看入口是否从 deserialize_struct 变成 deserialize_map;生命周期报错,看 borrowed lifetime 集合是否被字段属性扩大。宏生成代码很长,但故障点通常落在这几个稳定位置。
这套排查顺序也能帮助你写自定义反序列化。不要一开始就手写整个 Visitor,先把字段识别、临时存储、缺省策略和最终组装分开。Serde 生成器之所以长,是因为它把这些责任拆开后逐一处理;手写代码也应遵守同样边界。
这也是本章和第 4 章的连接点:Visitor 模式给了反向控制的接口,代码生成器则把接口所需的临时类型和临时状态全部补齐。没有生成器,用户要亲手维护这些状态。
因此,展开代码越长,越要先找状态变量和错误分支,而不是从第一行顺读到最后一行。
13.15 本章小结
Deserialize 生成比 Serialize 复杂 2.5 倍,根本原因是 Visitor 模式的"反向控制"要求每一种类型都要定义一个临时 Visitor struct,并为它实现 Visitor trait 的若干方法。
一个 struct 的 Deserialize 生成代码三部分:
- 字段标识符枚举 (
__Field+__FieldVisitor):识别输入中的字段 key(u64/str/bytes 三种形态) - 主 Visitor (
__Visitor):实现visit_seq和visit_map,把字段值组装成最终 struct - 入口调用:
deserializer.deserialize_struct("Name", FIELDS, __Visitor { ... })
属性的影响:
default/default = "fn"→ 组装阶段unwrap_or_elseskip_deserializing→ 字段不参与 __Field 和 visit_map,组装阶段用默认值deny_unknown_fields→ 移除 __ignore variant,unknown key 返回 erroralias = "..."→ __FieldVisitor 的 match 里多一个分支flatten→ 完全改写 visit_map 逻辑(第 16 章)borrow→ 字段类型从Option<String>变成Option<&'de str>(第 15 章)
enum 的四种 tag 策略——externally / internally / adjacently / untagged——各占一个文件(4 文件合计 778 行)、入口 enum_.rs 96 行做分派——是 ch14 的主题。
动手实验
- cargo expand 简单 struct:写一个 2 字段 struct,
cargo expand看完整生成代码。对照本章 13.4 节。 - 观察 deny_unknown_fields 效果:加上属性,对比生成代码差异——
__ignore消失、visit_str 返回错误。 - 观察 default 属性:字段加
#[serde(default)]或#[serde(default = "fn")],看组装阶段的变化。 - 读 de/struct_.rs:697 行代码,找到第 13.4 节 "主 Visitor" 生成的对应函数。理解它如何把各种属性分支组装成最终 quote! 模板。
延伸阅读
- serde_derive/src/de/struct_.rs 完整源码:最大的 de/ 子文件,读了这个文件你就读懂了 Deserialize 的核心。
- Serde "Implementing Deserialize":官方手写 Deserialize 教程。看完你能理解宏生成代码为什么长这样。
- IgnoredAny 文档:理解"消耗但丢弃"的特殊类型。
- 丛书卷一《Rust 编译器》第 14 章:系统讲解 Rust 宏展开算法——过程宏如何注册、TokenStream 如何输入输出、展开如何做不动点迭代——是理解 serde_derive 在编译器里是怎样被触发的前置阅读。