Serde 元编程
第 4 章 Deserializer 与 Visitor:反序列化的反向控制
4.1 反序列化为什么比序列化难
序列化是一个单向、确定的过程:你知道要写什么,只需要告诉格式怎么写。42u64.serialize(json_ser) 明确说"我是 u64,值是 42",JSON 格式照着写就行。信息流从数据结构流向格式。
反序列化反过来——你知道期待什么类型,但不知道输入里是什么。当你写 let x: User = serde_json::from_str(input)?,你告诉 Serde:"请给我一个 User",但 input 里可能是任意 JSON,可能缺字段、可能类型不匹配、可能完全不是对象。信息流从格式流向数据结构,而在沿途双方都需要协商。
这种信息不对称导致反序列化 API 比序列化复杂得多。看看数字对比:
| 指标 | 序列化 | 反序列化 |
|---|---|---|
| 核心 trait | Serializer(30 个 serialize_*;连同 7 个 SerializeXxx 子 trait 里的共 39 个) |
Deserializer(31 个 deserialize_*)+ Visitor(27 个 visit_*) |
| 状态机子 trait | 7 个(SerializeSeq 等) | 4 个(SeqAccess, MapAccess, EnumAccess, VariantAccess) |
| 核心机制 | 直接调用 | Visitor 模式(双向分派) |
| 生命周期参数 | 无 | 'de(借用输入数据) |
| 自描述识别 | 不需要 | deserialize_any |
本章要回答三个核心问题:
- 为什么反序列化需要 Visitor 模式?一次调用搞定不行吗?
'de生命周期在设计里起什么作用?- 自描述格式(JSON)和非自描述格式(Bincode)如何统一处理?
读完本章你会理解 Serde 里最常被讨论的一个设计——Visitor 模式。它不是 Serde 发明的(四人帮设计模式之一),Serde 把它用在了类型化的反序列化协议上。
4.2 Deserialize trait:看起来简单,实际深邃
先看最外层的 Deserialize trait:
// serde_core/src/de/mod.rs:554
pub trait Deserialize<'de>: Sized {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>;
}
两个关键细节已经出现:
1. 生命周期参数 'de。Serialize trait 没有任何生命周期参数;Deserialize 必须有。因为反序列化结果可能借用输入数据——比如从 &'a str 解析出的 &'a str。'de 是"deserializer 的输入数据存活多久"这个生命周期的名字。这个细节会在第 15 章专章讨论,这里只需要知道:Deserialize 的生命周期参数不是装饰,它决定了反序列化结果能否零拷贝。
2. Sized 约束。反序列化要返回 Self,所以必须知道大小。dyn Trait 无法反序列化——因为不知道要构造什么类型。
看一个最简单的 Deserialize 实现(bool):
// serde_core/src/de/impls.rs:70-77(配套的 `BoolVisitor` 在同文件 53-68 行)
impl<'de> Deserialize<'de> for bool {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,
{
deserializer.deserialize_bool(BoolVisitor)
}
}
只有一行实质逻辑:deserializer.deserialize_bool(BoolVisitor)。
这行代码藏着整个 Visitor 模式的本质——它把一个 Visitor 对象传给了 Deserializer。接下来的故事是:Deserializer 负责解析输入,然后回调 Visitor 的某个方法把解析结果传回来。这是一个明显的"双向分派"——数据结构端告诉格式端"你解析出来后调我的 Visitor",格式端解析完后反过来调数据结构的代码。
为什么要这么绕?继续读。
4.3 Visitor 模式的必要性
考虑一个朴素设计:如果 Deserializer::deserialize_bool 直接返回 Result<bool, Error>,那多简单——
// 假想的朴素设计
fn deserialize_bool(self) -> Result<bool, Self::Error>;
// 用户代码
impl<'de> Deserialize<'de> for bool {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_bool()
}
}
这看起来很合理。但问题来了——字符串怎么办? 字符串有两种 Rust 形态:&'de str(借用)和 String(拥有)。同一个 JSON 字符串,反序列化成哪种?
朴素设计需要两个方法:
// 假想
fn deserialize_str(self) -> Result<&'de str, Self::Error>;
fn deserialize_string(self) -> Result<String, Self::Error>;
行吗?看起来行。但 Deserializer 必须提前知道"能不能借用"。JSON 从 &'de str 来的时候可以借用,从 io::Read 流式读取时不能(数据读过就消失了)。如果用户请求 deserialize_str,而 Deserializer 恰好在流式模式下,必须动态拒绝。
现在加上一种复杂情况:用户定义了一个 MyString 类型,它既能从 &str 构造也能从 String 构造——怎么选择更高效的路径?
- 如果走
deserialize_str,拿到&str,立刻转成MyString::from(&str)——可能需要复制 - 如果走
deserialize_string,拿到String,MyString::from(String)可能直接 move,零复制
朴素设计下,MyString::deserialize 得先尝试 deserialize_str,如果 deserializer 拒绝(流式模式),再尝试 deserialize_string。这增加了错误处理和回溯——复杂。
Visitor 模式的解法:把"接受哪几种形式"的信息一次性给 Deserializer。
impl<'de> Deserialize<'de> for MyString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,
{
struct MyStringVisitor;
impl<'de> Visitor<'de> for MyStringVisitor {
type Value = MyString;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a string")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<MyString, E> {
Ok(MyString::from(v)) // 接受借用 str
}
fn visit_borrowed_str<E: de::Error>(self, v: &'de str) -> Result<MyString, E> {
Ok(MyString::from_borrowed(v)) // 接受 'de 借用
}
fn visit_string<E: de::Error>(self, v: String) -> Result<MyString, E> {
Ok(MyString::from_owned(v)) // 接受拥有 String——零拷贝
}
}
deserializer.deserialize_string(MyStringVisitor) // 提示"我想要字符串"
}
}
现在 Deserializer 可以自由选择:
- 从
&str输入解析、字符串里没有转义:调用visitor.visit_borrowed_str(s)(零拷贝;serde_json 的deserialize_string直接转给deserialize_str,见serde_json/src/de.rs:1553-1558,后者在:1540的Reference::Borrowed分支里调用它) - 从
io::Read流式解析、或者字符串带转义需要先解码:把内容放进内部 scratch 缓冲,调用visitor.visit_str(&scratch)(同文件Reference::Copied分支) - 格式手里已经有一个拥有所有权的
String(比如serde_json::Value::String反过来当 Deserializer 用,serde_json/src/value/de.rs:295):调用visitor.visit_string(s),把所有权直接交出
Visitor 一边有三个方法分别处理三种形式。Deserializer 选择最高效的一个调用。所有路径殊途同归——最终产出 MyString。
这就是 Visitor 模式的价值:把"我能接受什么形式"和"你能提供什么形式"解耦。Deserializer 不需要知道用户想要什么;Visitor 不需要知道输入来自哪种格式。它们通过一组预定义的 visit_* 方法协商。
设计意图:Visitor 模式是 Serde 对"多形式输入"的类型安全响应。它把"试错"(try deserialize_str,失败再 try deserialize_string)替换成"告诉我你支持什么"(visitor 的所有 visit_* 都可以被调用)。后者在编译期就排列好所有可能路径,单个值的反序列化在运行时不需要回溯。例外是 untagged enum(第 14 章):它先把输入缓冲成一份中间内容,再逐个变体尝试。
4.4 Deserializer trait:31 个 deserialize_* 方法(实测 serde 1.0.228)
有了 Visitor 概念,看 Deserializer trait 就清楚了:
// serde_core/src/de/mod.rs:945
pub trait Deserializer<'de>: Sized {
type Error: Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>;
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>;
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>;
// ... 共 31 个 deserialize_* 方法,实测排在 mod.rs 的 959~1184 行:
// deserialize_any + 28 个类型化入口(str/string、bytes/byte_buf 各占两个)
// + deserialize_identifier + deserialize_ignored_any
// 此外还有非 deserialize_* 的 is_human_readable(默认返回 true),
// 以及一个 #[doc(hidden)] 的内部方法 __deserialize_content_v1
}
结构特征:
- 返回类型不是数据本身,而是
V::Value。Visitor 的关联类型 Value 才是最终结果。Deserializer 不决定结果类型——它只解析输入并回调 Visitor。 selfby-value:和 Serializer 一样,Deserializer 用一次就消耗掉。- 带生命周期
'de:约束 Deserializer 的输入数据存活期。 - 每个方法都接收 visitor:这是协商机制。
一个重要的方法是 deserialize_any:
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where V: Visitor<'de>;
这是自描述格式的专用方法。含义是:"我不在乎你期待什么类型——你看我解析出什么,就调对应的 visit 方法"。
自描述 vs 非自描述:
- 自描述格式(JSON、YAML、TOML、MessagePack):输入里包含类型信息。看到
true就知道是 bool,看到42知道是数字,看到"hello"知道是字符串。deserialize_any能自动识别。 - 非自描述格式(Bincode、Postcard):输入是裸字节流,不带类型标签。必须提前知道期待什么类型才能解析。
deserialize_any没有可用的语义——Serde 自己的文档就把这一点写进了deserialize_any的注释里:"relying onDeserializer::deserialize_anymeans your data type will be able to deserialize from self-describing formats only, ruling out Postcard and many others"(serde_core/src/de/mod.rs:950-958)。具体到某个格式 crate 怎么拒绝(返回错误还是 panic),要看那个 crate 自己的实现,本专栏没有钉定它们的版本,不下断言。
这个差异决定了某些 Rust 类型只能用自描述格式。例如 serde_json::Value 是一个"任意 JSON 值"类型,它的 Deserialize 实现最后一行就是 deserializer.deserialize_any(ValueVisitor)(serde_json/src/value/de.rs:151)——所以 Value 不能从 Bincode 反序列化。
设计意图:
deserialize_any是 Serde 对"自描述"这一格式特性的显式表达:它把"由格式自己判断输入是什么"单独做成一个入口。但要注意,这不是编译期检查——deserialize_any是Deserializer的必需方法(serde_core/src/de/mod.rs:959-961,没有默认实现),非自描述格式也得实现它,怎么拒绝由各格式 crate 自己决定。所以依赖deserialize_any的类型配上非自描述格式照样能编译,要到运行时才失败。
4.5 Visitor trait:27 个 visit_* 方法(实测 serde 1.0.228)
Visitor 是反序列化的接收端:
// serde_core/src/de/mod.rs:1317
pub trait Visitor<'de>: Sized {
type Value;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result;
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where E: Error;
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where E: Error;
// ... 共 27 个 visit_* 方法(实测):12 数字(i8~i128、u8~u128、f32/f64)+ bool/char + 4 字符串/字节(含 borrowed_str / borrowed_bytes)+ string/byte_buf + none/some/unit + newtype_struct + seq/map/enum
}
关键细节:
1. type Value:Visitor 的"最终产物类型"。如果 visitor 是 BoolVisitor,type Value = bool;如果是 UserVisitor,type Value = User。
2. expecting 方法:必须实现,返回一个人类可读的"期待什么"字符串。用于错误信息——当 visit_str 被调用但 Visitor 只支持数字,Serde 可以生成 invalid type: string "abc", expected an integer 这种清晰错误(模板是 serde_core/src/de/mod.rs:214 的 "invalid type: {}, expected {}")。
3. visit_* 的默认实现分两类:"族长"报 invalid_type,窄类型转发给族长(serde_core/src/de/mod.rs:1348-1710 逐个可查):
fn visit_bool<E: Error>(self, v: bool) -> Result<Self::Value, E> {
Err(Error::invalid_type(Unexpected::Bool(v), &self))
}
逐条核过源码之后的完整名单是:visit_bool(serde_core/src/de/mod.rs:1352)、visit_i64(:1398)、visit_u64(:1460)、visit_f64(:1498)、visit_str(:1530)、visit_bytes(:1590)、visit_none(:1641)、visit_some(:1652)、visit_unit(:1662)、visit_newtype_struct(:1676)、visit_seq(:1687)、visit_map(:1698)、visit_enum(:1709)这 13 个"族长"默认返回 invalid_type——注意 visit_newtype_struct 也在这一列,它不会默认把 deserializer 转发下去,默认就是报错。同族的窄类型则默认转发给族长:visit_i8/i16/i32 转 visit_i64(:1364、:1376、:1388),visit_u8/u16/u32 转 visit_u64(:1426、:1438、:1450),visit_f32 转 visit_f64(:1488),visit_char 转 visit_str(:1512,走 v.encode_utf8),visit_borrowed_str/visit_string 转 visit_str(:1547、:1572),visit_borrowed_bytes/visit_byte_buf 转 visit_bytes(:1606、:1631)。剩下的 visit_i128/visit_u128 自成一族,默认用 Unexpected::Other("integer … as i128") 报 invalid_type(:1404-1415、:1466-1477)。这意味着 Visitor 只需要实现它"能接受"的方法——其他方法自动报错。写 BoolVisitor 只需要实现 visit_bool,其他 visit_i64、visit_str 等全部默认拒绝。这让 Visitor 的定义非常简洁。
4. 借用变体的 visit 方法:
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E>;
fn visit_borrowed_str<E: Error>(self, v: &'de str) -> Result<Self::Value, E>;
fn visit_string<E: Error>(self, v: String) -> Result<Self::Value, E>;
三种字符串形态各有一个方法。visit_str 接收借用(但不保证 'de);visit_borrowed_str 接收 'de 借用(可以零拷贝保留);visit_string 接收所有权。类似地 visit_bytes/visit_borrowed_bytes/visit_byte_buf 三种字节形态。
4.6 一个完整的 Visitor 实现例子
为了让 Visitor 具象化,看一个反序列化 i32 的完整 Visitor:
// 示意:serde 里 i32 的真实实现由 impl_deserialize_num! 宏生成(serde_core/src/de/impls.rs);
// 自己的 crate 不能再为 i32 写这个 impl(孤儿规则),动手试时换成一个 newtype
use serde::de::{self, Deserialize, Deserializer, Visitor};
use std::fmt;
impl<'de> Deserialize<'de> for i32 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,
{
struct I32Visitor;
impl<'de> Visitor<'de> for I32Visitor {
type Value = i32;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a 32-bit integer")
}
// 直接接受 i32
fn visit_i32<E: de::Error>(self, v: i32) -> Result<i32, E> {
Ok(v)
}
// i64 需要检查范围
fn visit_i64<E: de::Error>(self, v: i64) -> Result<i32, E> {
if v >= i32::MIN as i64 && v <= i32::MAX as i64 {
Ok(v as i32)
} else {
Err(E::invalid_value(
de::Unexpected::Signed(v),
&self,
))
}
}
// u32 也要检查(u32::MAX > i32::MAX)
fn visit_u32<E: de::Error>(self, v: u32) -> Result<i32, E> {
if v <= i32::MAX as u32 {
Ok(v as i32)
} else {
Err(E::invalid_value(
de::Unexpected::Unsigned(v as u64),
&self,
))
}
}
// u64 类似
fn visit_u64<E: de::Error>(self, v: u64) -> Result<i32, E> {
if v <= i32::MAX as u64 {
Ok(v as i32)
} else {
Err(E::invalid_value(de::Unexpected::Unsigned(v), &self))
}
}
// 其他 visit 方法自动返回 invalid_type
}
deserializer.deserialize_i32(I32Visitor)
}
}
这段代码体现了 Visitor 的几个典型模式:
模式 1:多种 visit 方法处理"相邻"类型。i32 可以从 i32、i64、u32、u64 合法转换(可能带范围检查)。Visitor 显式列出能接受的所有数值类型——如果输入是 f64,visitor 没实现 visit_f64,自动拒绝。
模式 2:范围检查 + invalid_value 错误。i64 值超出 i32 范围时,用 Error::invalid_value 报告。它和 invalid_type 一样,由 Serde 统一措辞("invalid value: …, expected …"),格式 crate 可以覆写。不过调用方拿到的通常只是一条消息:serde_json 的这两个方法最终都走 Error::custom(serde_json/src/error.rs:440-455),classify() 一律归为 Category::Data,分不出是哪一种。
模式 3:expecting 提供错误上下文。当 visit 拒绝时,错误信息会包含 self.expecting() 的描述:"a 32-bit integer",再加上 Unexpected::Signed(v) 的实际值——用户看到"invalid value: integer 5000000000, expected a 32-bit integer",立刻知道哪里出了问题。
这只是单个值的反序列化。复合类型(Vec、HashMap、struct)需要额外的状态机 trait——SeqAccess 和 MapAccess。
4.7 SeqAccess & MapAccess:读取侧的状态机
序列化侧有 SerializeSeq 等 7 个子 trait。反序列化侧只有 4 个:SeqAccess、MapAccess、EnumAccess、VariantAccess。为什么这么少?
因为反序列化时,tuple 和 seq 对数据结构来说没区别——反序列化侧读到的都是"一个元素一个元素地给我",元素是同类型还是异类型由 Visitor 自己在每次 next_element 时判断。同理 struct 和 map 在读取侧看起来都是"一对 key-value 一对 key-value 地给我"。所以读取状态机只需要 2 种主要抽象(Seq 和 Map),外加 2 种 enum 专用。
SeqAccess 的定义(serde_core/src/de/mod.rs:1749-1781,下面删去了文档注释与 #[inline]):
pub trait SeqAccess<'de> {
type Error: Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error>
where T: DeserializeSeed<'de>;
fn next_element<T>(&mut self) -> Result<Option<T>, Self::Error>
where T: Deserialize<'de>
{
self.next_element_seed(PhantomData) // 默认实现
}
fn size_hint(&self) -> Option<usize> { None }
}
用法:Visitor 在 visit_seq 方法里从 SeqAccess 逐个取元素:
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where A: SeqAccess<'de>
{
let mut vec = Vec::new();
while let Some(elem) = seq.next_element::<i32>()? {
vec.push(elem);
}
Ok(vec)
}
关键细节:
next_element返回Option<T>:None表示序列结束,Some(v)表示下一个元素。next_element是泛型方法,调用方每次可以选择不同的 T 类型——支持异构序列(比如 tuple(i32, String, bool))。size_hint是性能提示:如果 Deserializer 知道序列长度(JSON 数组在解析完前不知道,但某些格式知道),可以提前告诉 Visitor,Visitor 就能预分配容量。
MapAccess 类似,但有 key-value 两步(serde_core/src/de/mod.rs:1837-1940):
pub trait MapAccess<'de> {
type Error: Error;
fn next_key_seed<K: DeserializeSeed<'de>>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>;
fn next_value_seed<V: DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value, Self::Error>;
// 以及便捷方法 next_entry_seed, next_key, next_value, next_entry, size_hint
}
只有 next_key_seed、next_value_seed 两个是必须实现的;其余四个都有默认实现——next_entry_seed 就是把两者串起来(serde_core/src/de/mod.rs:1882-1888:先 next_key_seed,拿到 Some(key) 再 next_value_seed,拿到 None 就返回 None),next_key/next_value/next_entry 分别用 PhantomData 桥接到对应的 *_seed(:1901、:1918、:1932),size_hint 默认返回 None(:1937-1939,和 SeqAccess::size_hint 一样)。
分开的原因和序列化侧一样——有时需要先读 key 判断,再决定 value 的类型(特别是 enum 的 tag 模式)。
4.7.1 DeserializeSeed:把状态带进反序列化
上面 next_element_seed 签名里那个 DeserializeSeed<'de> 是什么?它和 Deserialize 的关系要专门说清楚——这是 Serde 容易被忽略的一个设计点,一些边角场景(比如下面的拍平嵌套数组)靠它省掉中间分配。
两者的差异是一行字,但含义完全不同:
// serde_core/src/de/mod.rs:803(删去了文档注释)
pub trait DeserializeSeed<'de>: Sized {
type Value;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where D: Deserializer<'de>;
}
对比 Deserialize::deserialize(deserializer: D) 是类方法(没有 self),DeserializeSeed::deserialize(self, deserializer: D) 带着 self 进入反序列化。一句话区分:
Deserialize:从无状态开始反序列化——每次调用都产出一个全新值。DeserializeSeed:带着已有状态("种子")反序列化——可以把结果累积进去、复用现有容器、或者从外部给出类型提示。
为什么需要这个?两个典型场景:
1. Append-only 反序列化避免分配。普通 Vec<i32>::deserialize 每次都 Vec::new() 再 push。如果你从 JSON 里反序列化一个嵌套数组 [[1,2], [3,4], [5,6]] 想拍平成单个 Vec<i32>,用 Deserialize 只能先得到 Vec<Vec<i32>> 再 flatten——中间过了一次 Vec-of-Vecs 的分配。用 DeserializeSeed 写一个"把下一个内层数组追加进我这个已有 Vec"的 seed,就能一次性拍平:
struct ExtendVec<'a, T>(&'a mut Vec<T>);
impl<'de, T: Deserialize<'de>> DeserializeSeed<'de> for ExtendVec<'_, T> {
type Value = (); // 没有新产物——副作用在 self.0 上
fn deserialize<D: Deserializer<'de>>(self, d: D) -> Result<(), D::Error> {
// ... visit_seq 里 self.0.push(elem)
}
}
外层 visitor 里 while let Some(()) = seq.next_element_seed(ExtendVec(&mut vec))?——每次把内层数组直接 append 到同一个 vec 里。serde_core/src/de/mod.rs:695-796 的文档示例给出了完整代码。
2. 运行时决定类型的反序列化。JSON 里某个字段的类型要按上下文推断(比如第一列是 type_tag 字段,后面的 value 按 tag 选择类型解析),静态 Deserialize 没法表达"我反序列化前要先看一眼"。DeserializeSeed 能——seed 可以持有"我要解析成什么类型"的运行时信息。
next_element 的"免费"桥接。读者可能好奇:那前面写的 seq.next_element::<i32>() 是怎么回事?答案在 serde_core/src/de/mod.rs:814:
impl<'de, T> DeserializeSeed<'de> for PhantomData<T>
where T: Deserialize<'de>,
{
type Value = T;
#[inline]
fn deserialize<D>(self, deserializer: D) -> Result<T, D::Error>
where D: Deserializer<'de>,
{
T::deserialize(deserializer)
}
}
PhantomData<T> 被免费实现成 DeserializeSeed——它是个零大小类型、没有状态、deserialize 直接把 deserializer 转发给 T::deserialize。这让 next_element 的默认实现成立:
fn next_element<T>(&mut self) -> Result<Option<T>, Self::Error>
where T: Deserialize<'de>
{
self.next_element_seed(PhantomData) // 把无状态包装成零成本 seed
}
整个 SeqAccess 只需实现 next_element_seed 一个方法,用 PhantomData 桥接就同时有了 next_element。这是 Serde"用零大小类型把概念分层、避免 API 膨胀"风格的典型例子——和第 3 章靠空 enum Void 做成的 Impossible 同源同宗、都是用类型系统而非运行时标志来做结构区分。
4.8 完整调用追踪:反序列化 Vec<i32> 从 JSON [1,2,3]
用时序图看 serde_json::from_str::<Vec<i32>>("[1,2,3]") 发生了什么:
sequenceDiagram
participant U as 用户代码
participant VD as "Vec[i32]::deserialize"
participant D as JsonDeserializer
participant VV as "VecVisitor[i32]"
participant SA as JsonSeqAccess
participant ID as i32::deserialize
participant IV as I32Visitor
U->>D: from_str("[1,2,3]")
D->>VD: Vec::deserialize(deserializer)
VD->>D: deserializer.deserialize_seq(VecVisitor)
D->>D: 读到 '[', 创建 SeqAccess
D->>VV: visitor.visit_seq(seq_access)
VV->>SA: seq.next_element (T = i32)
SA->>ID: i32::deserialize(elem_deserializer)
ID->>D: deserializer.deserialize_i32(I32Visitor)
D->>D: 读到 "1", 解析成 u64
D->>IV: visitor.visit_u64(1)
IV-->>D: Ok(1)
D-->>ID: Ok(1)
ID-->>SA: Ok(1)
SA-->>VV: Ok(Some(1))
Note over VV,SA: 重复读 2, 3
VV->>SA: seq.next_element (T = i32)
SA->>D: 读到 ']'
SA-->>VV: Ok(None)
VV-->>D: Ok(vec[1,2,3])
D-->>VD: Ok(vec[1,2,3])
VD-->>U: Ok(vec[1,2,3])
几个观察:
1. 两层 Visitor。 外层 VecVisitor 处理 "我期待一个序列",内层 I32Visitor 处理每个元素的"我期待 i32"。Visitor 递归嵌套对应数据结构的递归嵌套。注意 serde_json 并不会调用 visit_i32:它把整数统一解析成 u64/i64,然后调 visit_u64 或 visit_i64(serde_json/src/de.rs:124-125),范围收窄交给 Visitor 一侧——这正是 4.6 节的 I32Visitor 要实现 visit_u64/visit_i64 的原因。
2. Deserializer 实例传递。 外层 Deserializer 是整个 JSON;内层反序列化 i32 时,JsonDeserializer 本身也作为(内部子)Deserializer 传给 i32::deserialize。同一个 Deserializer 贯穿多层,但每次方法调用消耗 self。serde_json 是为 &mut Deserializer<R> 实现 de::Deserializer 的(serde_json/src/de.rs:1389),SeqAccess 每取一个元素就传一次 &mut *self.de(serde_json/src/de.rs:1965)——被消耗的只是这次重借用,底层的 Deserializer 并没有被销毁,读取位置则随之前进。
3. None 表示终止。 SeqAccess::next_element 返回 None 而不是错误——终止是正常流程,不是异常。这和 Rust Iterator 的 next() 返回 None 是一个思路。
4. 错误会一路向上传播——但拒绝不一定发生在 Visitor 一侧。 如果 JSON 里某个元素不是数字而是字符串,走 deserialize_i32 这条类型化入口时 visit_u64 固然不会被调用,visit_str 也不会:serde_json 的所有整数入口都由 deserialize_number! 宏展开到 Deserializer::deserialize_number(serde_json/src/de.rs:319-343),它 peek 到的首字节不是 - 或数字时直接走 _ => Err(self.peek_invalid_type(&visitor))(serde_json/src/de.rs:336),而 peek_invalid_type 自己把字符串读出来、直接构造 de::Error::invalid_type(Unexpected::Str(&s), exp)(serde_json/src/de.rs:303-310)——visitor 在这里只被当作 Expected 用(提供 expecting 的措辞),一个 visit_* 都没被调用。真正会调到 visit_str/visit_borrowed_str 的是 deserialize_any 这条路(serde_json/src/de.rs:1429-1430)。两条路的错误文本几乎一样,机制却不同:类型化入口由格式层拒绝,deserialize_any 由 Visitor 拒绝。错误无论从哪产生,都经过 ? 一路返回到 from_str。
4.9 Unexpected 与 Expected:结构化错误的基础
前面多次提到 Error::invalid_type 和 Unexpected 枚举。它们是 Serde 错误系统的核心:
// serde_core/src/de/mod.rs:338
pub enum Unexpected<'a> {
Bool(bool),
Unsigned(u64),
Signed(i64),
Float(f64),
Char(char),
Str(&'a str),
Bytes(&'a [u8]),
Unit,
Option,
NewtypeStruct,
Seq,
Map,
Enum,
// ...
}
Unexpected 描述"实际收到了什么"。它是一个简化的类型分类——所有 u8/u16/u32/u64 合并为 Unsigned(u64),所有 i8/i16/i32/i64 合并为 Signed(i64)——因为错误信息里精度细节不重要。
Expected 是一个 trait(serde_core/src/de/mod.rs:484-497):
pub trait Expected {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result;
}
impl<'de, T: Visitor<'de>> Expected for T {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
self.expecting(formatter)
}
}
注意:任何 Visitor 自动实现 Expected(通过 expecting 方法)。这让错误函数可以直接接受 &visitor 作为"期待什么"的说明。
所以错误构造变成:
Err(E::invalid_type(Unexpected::Str("hello"), &self))
// 生成: invalid type: string "hello", expected a 32-bit integer
注意字符串这一支用的是 {:?}(serde_core/src/de/mod.rs:410 的 Str(s) => write!(formatter, "string {:?}", s)),所以值被双引号包起来;数字、布尔、字符那几支用的是反引号(:405-409),出来的是 integer `42` 、boolean `true` 这种形状。
这套结构化错误的价值在于——即使你深度嵌套(struct 里的 enum 里的 Option 里的 i32),错误信息也能说清"实际收到了什么、期待的是什么"。至于出错的位置,是格式自己补上的:serde_json 会在消息后面附上 at line 1 column 10 这样的行列号。
Error trait 还提供了其他构造函数:invalid_value(serde_core/src/de/mod.rs:231)、invalid_length(serde_core/src/de/mod.rs:245)、unknown_variant(serde_core/src/de/mod.rs:252)、unknown_field(serde_core/src/de/mod.rs:270)、missing_field(serde_core/src/de/mod.rs:289)、duplicate_field(serde_core/src/de/mod.rs:296);此外还有 custom,是这个 trait 里唯一没有默认实现、必须由格式 crate 自己写的一个(serde_core/src/de/mod.rs:195)。整个 trait 写在 declare_error_trait! 宏体里(serde_core/src/de/mod.rs:165-299),因为 std 与 no_std 下的 supertrait 不同——serde_core/src/de/mod.rs:303-307 两次调用这个宏,分别展开成 Error: Sized + StdError 和 Error: Sized + Debug + Display。
这几个构造函数每一个都对应 Serde 反序列化的一类典型错误场景,合起来是 反序列化错误的分类法,第 13 章讲派生代码如何处理缺字段、重复字段时会回到这个话题。
4.10 DeserializeOwned 与 'de:何时能借用
前面提到 Deserialize 的生命周期参数 'de。什么时候会有"可以借用的场景"?什么时候只能拥有?
Serde 提供了一个辅助 trait:
// serde_core/src/de/mod.rs:632
pub trait DeserializeOwned: for<'de> Deserialize<'de> {}
impl<T> DeserializeOwned for T where T: for<'de> Deserialize<'de> {}
DeserializeOwned 表示"不依赖借用"。形式上是"对任意 'de 都能反序列化"——通过 for<'de> 高阶生命周期 bound 表达。
用法区别:
// 可能借用输入数据的反序列化
fn from_str<'a, T: Deserialize<'a>>(s: &'a str) -> Result<T, Error>;
// 必须完全拥有(无法借用流式数据)
fn from_reader<R: Read, T: DeserializeOwned>(rdr: R) -> Result<T, Error>;
from_str 收到 &'a str,返回的 T 可以借用这个 &'a str——比如 T = &'a str 或 T = MyStruct { name: &'a str }。
from_reader 从 io::Read 流式读取,数据在读取过程中会被丢弃,不存在可借用的长期数据。所以 T 必须是 DeserializeOwned——不能借用任何东西。
实际影响:
use serde::Deserialize;
#[derive(Deserialize)]
struct Borrowed<'a> { name: &'a str }
#[derive(Deserialize)]
struct Owned { name: String }
fn main() {
let s = r#"{"name":"alice"}"#;
let b: Borrowed = serde_json::from_str(s).unwrap(); // OK, 借用 s
let o: Owned = serde_json::from_str(s).unwrap(); // OK, 拥有 String
let reader = std::fs::File::open("data.json").unwrap();
// let b: Borrowed = serde_json::from_reader(reader).unwrap(); // 编译错误!Borrowed 不是 DeserializeOwned
let o: Owned = serde_json::from_reader(reader).unwrap(); // OK
}
这个约束在编译期就能检查出来——不会有运行时惊喜。
第 15 章会完整展开 'de 的生命周期故事,包括 visit_str vs visit_borrowed_str 的差异、Cow<str> 的特殊处理、以及 #[serde(borrow)] 属性的作用。这里只需要记住:'de 不是装饰,它是 Serde 零拷贝能力的类型级证明。
4.11 实测:serde_core/src/de/ 7698 行 vs serde_core/src/ser/ 3441 行
把 serde_core 内 de/ 和 ser/ 两个子模块的实测行数并列——
| 模块 | 文件数 | 行 | 主要文件 |
|---|---|---|---|
serde_core/src/de/ |
4 | 7698 | mod.rs 2392(trait 定义)+ impls.rs 3173(内置类型 impl Deserialize)+ value.rs 1895(让原语类型直接当 Deserializer 用)+ ignored_any.rs 238(IgnoredAny 类型) |
serde_core/src/ser/ |
4 | 3441 | mod.rs 2010(trait 定义)+ impls.rs 1045(内置类型 impl Serialize)+ impossible.rs 216(Impossible 用于不可能的 SerializeXxx)+ fmt.rs 170(基于 Display 的 serializer) |
| 比例 | — | 2.24x | 反序列化代码量 = 序列化的 2.24 倍 |
两条值得记住的事实:
serde_core/src/de/impls.rs3173 行 ≈serde_core/src/de/mod.rs2392 行 × 1.32——内置类型的反序列化实现(Vec/HashMap/Option/Box/Arc/Cow/BTreeMap/Tuple/Array/...)比 trait 定义本身还多三分之一——因为反序列化侧每个类型都要配一个 Visitor:expecting+ 能接受的visit_*+ 错误路径,而序列化侧一个类型只需要一次serialize调用。数值类型要在visit_i8~visit_u128、visit_f32/f64之间做范围检查(impl_deserialize_num!、int_to_int!、uint_to_self!等宏),集合类型靠seq_impl!/map_impl!/array_impls!/tuple_impls!宏批量展开,还有 IP 地址、SocketAddr、Duration、Range、原子类型各自的 Visitor。注意Vec<T>的VecVisitor只实现expecting+visit_seq(serde_core/src/de/impls.rs:1147-1170),HashMap的MapVisitor只实现expecting+visit_map(serde_core/src/de/impls.rs:1523-1548,在map_impl!宏体里)——多出来的行数不是"一个类型接受多种输入",而是"每个类型都要自带一台接收机",这是 Visitor 模式的工程代价serde_core/src/de/value.rs1895 行——一个独立子系统——它定义StrDeserializer、U32Deserializer、SeqAccessDeserializer、MapAccessDeserializer等包装类型,并通过IntoDeserializertrait 让&str、u32、String、Vec这些原语反过来当 Deserializer 用——serde/src/private/de.rs里支撑 untagged/internally tagged enum 与 flatten 的ContentDeserializer也复用了其中一部分,如SeqAccessDeserializer、BytesDeserializer(第 14、16 章);ser 侧没有等价文件——序列化没有"让 i32 当 Serializer 用"的需求
对比 §10.13 实测的 serde_derive/(8969 行)——serde_core 的 ser/ + de/ 两个子模块合计 11139 行(3441 + 7698;整个 serde_core/src/ 连 lib.rs 等一起是 12037 行)+ serde_derive 8969 + proc-macro2 6029(§6.12)= 26137 行才让 #[derive(Serialize, Deserialize)] 跑起来(口径与 §1.10 那张表一致)——印证 §6.12 末尾 "基础设施的字面意义"。
4.12 serde_json 的入口选择:自描述格式如何调度 Visitor
反序列化最容易误解的一点是:Deserializer 不是简单地"读一个值然后返回"。它要在输入字节、目标类型和 Visitor 之间做一次调度。serde_json 是自描述格式,源码把这件事写得非常直观。
serde_json/src/de.rs:1389-1408 为 &mut Deserializer<R> 实现 de::Deserializer,deserialize_any 的第一步是 parse_whitespace() 后看下一个字节:n 走 visit_unit,t/f 走 bool," 走字符串,[ 走 seq,{ 走 map。也就是说,自描述格式可以先观察输入,再选择 Visitor 方法。
但 Serde 仍然保留类型化入口。serde_json/src/de.rs:1820-1866 的 deserialize_struct 只接受 [ 或 {,然后分别调用 visitor.visit_seq 或 visitor.visit_map;如果下一个字节不是这两类,直接报 invalid_type。serde_json/src/de.rs:1871-1901 的 deserialize_enum 只接受两种开头:{ 走 visitor.visit_enum(VariantAccess::new(self)),因为默认外部 tag 的 JSON 是 {"Variant": ...};" 走 visitor.visit_enum(UnitVariantAccess::new(self)),对应 unit variant 直接写成裸字符串 "Variant"。serde_json/src/de.rs:1903-1908 的 deserialize_identifier 只是转到 deserialize_str,说明字段名/变体名在 JSON 中本质上是字符串。
flowchart TD
A[目标类型调用 deserialize_xxx] --> B{入口类型}
B -->|deserialize_any| C[先看 JSON 下一个字节]
B -->|deserialize_struct| D[只接受数组或对象]
B -->|deserialize_enum| E[只接受外部 tag 对象或裸字符串]
C --> F[调用 visit_bool/visit_str/visit_seq/visit_map]
D --> G[调用 visit_seq 或 visit_map]
E --> H[调用 visit_enum]
这段源码给本章前面的抽象补上了工程边界:
| 场景 | 推荐入口 | serde_json 行为 |
设计含义 |
|---|---|---|---|
serde_json::Value |
deserialize_any |
根据字节动态分派 | Value 想保留输入原貌,必须让格式先观察 |
| 普通 struct | deserialize_struct |
限定数组或对象 | 目标类型已知,不需要接受任意 JSON |
| enum 默认表示 | deserialize_enum |
期待 {"Variant": ...} 对象或裸字符串(unit variant) |
让派生代码拿到 variant 名再进 variant payload |
| 字段名 | deserialize_identifier |
转成字符串路径 | 字段识别交给派生出的 __FieldVisitor |
非自描述格式会做相反选择。以 bincode 这类格式为例,输入字节里没有"这是字符串还是整数"的显式 tag,必须由目标类型告诉格式下一步读什么。Serde 的 API 同时存在 deserialize_any 和几十个 deserialize_*,就是为了让 JSON 这类格式有动态入口,也让 bincode 这类格式保持类型驱动入口。
serde_json/src/de.rs:2351 还定义了 StreamDeserializer,把连续 JSON 值包装成迭代器;serde_json/src/de.rs:2434 起的 impl Iterator,其 next 每次跳过空白再尝试解析一个 T。这说明 Deserializer 不只是"一次性 from_str" 的内部实现,它也能作为流式协议的游标。第 17 章 §17.14 讲日志流和 LLM 输出时,这个迭代器会再次出现。
顶层 from_str 也能说明生命周期为什么属于 Deserializer 协议的一部分。serde_json/src/de.rs:2699-2704 的签名是 pub fn from_str<'a, T>(s: &'a str) -> Result<T> where T: Deserialize<'a>,返回类型 T 可以从输入字符串借用数据。这里没有额外的运行时句柄保存输入,安全性完全靠 'a 写进 trait bound。换成 from_reader 时,输入来自 I/O 缓冲,不能把临时缓冲借给返回值,所以 API 会要求 DeserializeOwned。第 15 章会专门展开这个差异;在本章,只要记住一点:Visitor 不是单纯的设计模式,它也是生命周期和所有权协商的载体。
4.13 本章小结
Deserializer 和 Visitor 是 Serde 反序列化侧的两个核心抽象。它们协同工作的模式叫 Visitor 模式:
- Deserialize 实现者创建一个 Visitor,告诉 Deserializer"我期待这类数据"。
- Deserializer 解析输入,根据看到的内容调用 Visitor 的对应
visit_*方法。 - Visitor 的
visit_*方法把收到的值转换成type Value并返回。 - 如果类型不匹配(比如期待整数但收到字符串),默认
visit_*实现返回invalid_type错误。
这套设计解决了序列化没有的三个问题:
- 多形式输入(
&strvsStringvs&'de str):Visitor 有三个 visit_* 方法,Deserializer 选最高效的调用。 - 自描述 vs 非自描述:
deserialize_any让自描述格式可以"动态识别",非自描述格式则坚持"提前声明期待"。 - 借用与所有权:
'de生命周期参数把借用关系写进类型系统,DeserializeOwned是"不借用"的显式标记。
比较序列化与反序列化的对称性:
| 方面 | 序列化 | 反序列化 |
|---|---|---|
| 入口 | Serialize::serialize |
Deserialize::deserialize |
| 格式侧 trait | Serializer(30 个 serialize_*) |
Deserializer(31 个 deserialize_*) |
| 数据侧 trait | Serialize |
Visitor(间接,由 Deserialize 创建) |
| 状态机 | 7 个 SerializeXxx | 4 个 Access trait |
| 生命周期 | 无 | 'de |
| 信息流向 | 数据→格式 | 格式→数据(通过 Visitor 回调) |
第 2-4 章完成了 Data Model 层的全部内容。你现在知道:Data Model 是 29 种原语的中间表示、Serializer/Deserializer 是格式侧的 trait 入口、Visitor 模式解决反序列化的多形式协商问题。
下一章起 我们暂时离开 Serde,去构建过程宏的完整知识体系。因为要真正理解 #[derive(Serialize)] 的实现(第 10-14 章),必须先懂 Rust 宏系统、TokenStream、syn、quote 这一整套工具链——这就是第三部分"过程宏工程学"。
动手实验
-
实现一个
NonZeroI32反序列化:这是一个值不能为 0 的 i32。写一个 Visitor,接受visit_i64和visit_u64(serde_json 不会调用visit_i32,见 §4.8;范围检查参照 §4.6),如果收到 0 就用invalid_value报错。测试三种情况:有效值、零值、字符串输入,观察错误信息的差异。 -
自描述 vs 非自描述对比(用 bincode 1.x 的 API):
let json_input = r#"{"x":42}"#; let v1: serde_json::Value = serde_json::from_str(json_input).unwrap(); // OK let bin = bincode::serialize(&v1).unwrap(); let v2: Result<serde_json::Value, _> = bincode::deserialize(&bin); // 能编译,结果是什么?查明 bincode 如何拒绝
Value。 -
思考题:为什么
Visitor::type Value需要关联类型而不是泛型参数?(提示:visitor 对象通常短暂,type 固定;泛型会让 visitor 可以被"任意 Value"复用,引入不必要的复杂性。)
延伸阅读
- Serde "Understanding deserializer lifetimes":关于
'de的权威讨论,本章只是引入,该文章是完整论述。 - Serde "Implementing a Deserializer":从零实现 Deserializer 的官方教程。
- GoF Visitor 模式:理解经典 OO Visitor,对比 Serde 的类型版本。
serde_core/src/de/mod.rs的 1317-1720 行:Visitor trait 的完整定义和默认实现,读一遍对"27 个 visit_* 如何相互 fallback" 有完整认识。- 《Rust 编译器与运行时揭秘》专栏第 4 章"生命周期:编译器如何推断引用的有效范围":本章
'de话题的编译器视角补充。