Serde 元编程

第 4 章 Deserializer 与 Visitor:反序列化的反向控制

作者 杨艺韬 · 8,827 字 · 发布于 · 更新于

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

本章要回答三个核心问题:

  1. 为什么反序列化需要 Visitor 模式?一次调用搞定不行吗?
  2. 'de 生命周期在设计里起什么作用?
  3. 自描述格式(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
}

结构特征:

  1. 返回类型不是数据本身,而是 V::Value。Visitor 的关联类型 Value 才是最终结果。Deserializer 不决定结果类型——它只解析输入并回调 Visitor。
  2. self by-value:和 Serializer 一样,Deserializer 用一次就消耗掉。
  3. 带生命周期 'de:约束 Deserializer 的输入数据存活期。
  4. 每个方法都接收 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 on Deserializer::deserialize_any means 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 倍

两条值得记住的事实:

  1. serde_core/src/de/impls.rs 3173 行 ≈ serde_core/src/de/mod.rs 2392 行 × 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 模式的工程代价
  2. serde_core/src/de/value.rs 1895 行——一个独立子系统——它定义 StrDeserializer、U32Deserializer、SeqAccessDeserializer、MapAccessDeserializer 等包装类型,并通过 IntoDeserializer trait 让 &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 模式:

  1. Deserialize 实现者创建一个 Visitor,告诉 Deserializer"我期待这类数据"。
  2. Deserializer 解析输入,根据看到的内容调用 Visitor 的对应 visit_* 方法。
  3. Visitor 的 visit_* 方法把收到的值转换成 type Value 并返回。
  4. 如果类型不匹配(比如期待整数但收到字符串),默认 visit_* 实现返回 invalid_type 错误。

这套设计解决了序列化没有的三个问题:

  • 多形式输入(&str vs String vs &'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 这一整套工具链——这就是第三部分"过程宏工程学"。

动手实验

  1. 实现一个 NonZeroI32 反序列化:这是一个值不能为 0 的 i32。写一个 Visitor,接受 visit_i64 和 visit_u64(serde_json 不会调用 visit_i32,见 §4.8;范围检查参照 §4.6),如果收到 0 就用 invalid_value 报错。测试三种情况:有效值、零值、字符串输入,观察错误信息的差异。

  2. 自描述 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。

  3. 思考题:为什么 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 话题的编译器视角补充。