Skip to content

第 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 比序列化复杂一个数量级。看看数字对比:

指标序列化反序列化
核心 traitSerializer (39 方法)Deserializer (31 方法) + Visitor (27 方法)
状态机子 trait7 个(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:

rust
// serde/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):

rust
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>,那多简单——

rust
// 假想的朴素设计
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 字符串,反序列化成哪种?

朴素设计需要两个方法:

rust
// 假想
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,拿到 StringMyString::from(String) 可能直接 move,零复制

朴素设计下,MyString::deserialize 得先尝试 deserialize_str,如果 deserializer 拒绝(流式模式),再尝试 deserialize_string。这增加了错误处理和回溯——复杂。

Visitor 模式的解法:把"接受哪几种形式"的信息一次性给 Deserializer。

rust
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 可以自由选择

  • 流式 JSON 解析:调用 visitor.visit_string(s)(临时构造的 String)
  • 借用源 JSON 解析:调用 visitor.visit_borrowed_str(&input_slice)(零拷贝)
  • 内存紧张场景:调用 visitor.visit_str(&temp)(临时 &str

Visitor 一边有三个方法分别处理三种形式。Deserializer 选择最高效的一个调用。所有路径殊途同归——最终产出 MyString

这就是 Visitor 模式的价值:把"我能接受什么形式"和"你能提供什么形式"解耦。Deserializer 不需要知道用户想要什么;Visitor 不需要知道输入来自哪种格式。它们通过一组预定义的 visit_* 方法协商。

设计意图:Visitor 模式是 Serde 对"多形式输入"的类型安全响应。它把"试错"(try deserialize_str,失败再 try deserialize_string)替换成"告诉我你支持什么"(visitor 的所有 visit_* 都可以被调用)。后者在编译期就排列好所有可能路径,运行时没有回溯。

4.4 Deserializer trait:31 个 deserialize_* 方法(实测 serde 1.0.228)

有了 Visitor 概念,看 Deserializer trait 就清楚了:

rust
// serde/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_* 方法(实测,覆盖 29 种 Data Model 原语 + identifier + ignored_any)
}

结构特征:

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

一个重要的方法是 deserialize_any

rust
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 无法实现——它们的 deserialize_any 通常返回"Deserializer does not support self-describing formats"错误。

这个差异决定了某些 Rust 类型只能用自描述格式。例如 serde_json::Value 是一个"任意 JSON 值"类型,它的 Deserialize 实现调用 deserialize_any——所以 Value 不能从 Bincode 反序列化。

设计意图deserialize_any 是 Serde 对"自描述"这一格式特性的类型级表达。它让"能否解析任意类型"从运行时问题变成编译期能力查询——如果你的类型依赖 deserialize_any,那它就不能用于非自描述格式,而且编译不过时错误信息清晰。

4.5 Visitor trait:27 个 visit_* 方法(实测 serde 1.0.228)

Visitor 是反序列化的接收端:

rust
// serde/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_* 方法(实测):13 数字 + bool/char + 4 字符串/字节(含 borrowed_str / borrowed_bytes)+ string/byte_buf + none/some/unit + newtype_struct + seq/map/enum
}

关键细节:

1. type Value:Visitor 的"最终产物类型"。如果 visitor 是 BoolVisitortype Value = bool;如果是 UserVisitortype Value = User

2. expecting 方法:必须实现,返回一个人类可读的"期待什么"字符串。用于错误信息——当 visit_str 被调用但 Visitor 只支持数字,Serde 可以生成 "invalid type: string, expected an integer" 这种清晰错误。

3. 所有 visit_* 默认实现是返回 invalid_type 错误

rust
fn visit_bool<E: Error>(self, v: bool) -> Result<Self::Value, E> {
    Err(Error::invalid_type(Unexpected::Bool(v), &self))
}

这意味着 Visitor 只需要实现它"能接受"的方法——其他方法自动报错。写 BoolVisitor 只需要实现 visit_bool,其他 visit_i64visit_str 等全部默认拒绝。这让 Visitor 的定义非常简洁。

4. 借用变体的 visit 方法

rust
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:

rust
use serde::de::{self, 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 可以从 i32i64u32u64 合法转换(可能带范围检查)。Visitor 显式列出能接受的所有数值类型——如果输入是 f64,visitor 没实现 visit_f64,自动拒绝。

模式 2:范围检查 + invalid_value 错误i64 值超出 i32 范围时,用 Error::invalid_value 报告。这种结构化错误比字符串更好——调用方可以根据错误类型做不同处理。

模式 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——SeqAccessMapAccess

4.7 SeqAccess & MapAccess:读取侧的状态机

序列化侧有 SerializeSeq 等 7 个子 trait。反序列化侧只有 4 个:SeqAccessMapAccessEnumAccessVariantAccess。为什么这么少?

因为反序列化时,tupleseq 对数据结构来说没区别——反序列化侧读到的都是"一个元素一个元素地给我",元素是同类型还是异类型由 Visitor 自己在每次 next_element 时判断。同理 structmap 在读取侧看起来都是"一对 key-value 一对 key-value 地给我"。所以读取状态机只需要 2 种主要抽象(Seq 和 Map),外加 2 种 enum 专用。

SeqAccess 的定义:

rust
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 逐个取元素:

rust
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 两步:

rust
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_key, next_value, next_entry
}

分开的原因和序列化侧一样——有时需要先读 key 判断,再决定 value 的类型(特别是 enum 的 tag 模式)。

4.7.1 DeserializeSeed:把状态带进反序列化

上面 next_element_seed 签名里那个 DeserializeSeed<'de> 是什么?它和 Deserialize 的关系要专门说清楚——这是 Serde 最被低估的设计点之一,也是"为什么 Serde 在边角场景仍然零开销"的关键。

两者的差异是一行字,但含义完全不同:

rust
// serde_core/src/de/mod.rs:803(保留了 Serde 原始注释风格)
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,就能一次性拍平:

rust
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 里。官方 de/mod.rs:705-786 给了完整示例。

2. 运行时决定类型的反序列化。JSON 里某个字段的类型要按上下文推断(比如第一列是 type_tag 字段,后面的 value 按 tag 选择类型解析),静态 Deserialize 没法表达"我反序列化前要先看一眼"。DeserializeSeed 能——seed 可以持有"我要解析成什么类型"的运行时信息。

next_element 的"免费"桥接。读者可能好奇:那前面写的 seq.next_element::<i32>() 是怎么回事?答案在 de/mod.rs:814

rust
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 的默认实现成立:

rust
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 章 Impossible 空 enum 同源同宗、都是用类型系统而非运行时标志来做结构区分。

4.8 完整调用追踪:反序列化 Vec<i32> 从 JSON [1,2,3]

用时序图看 serde_json::from_str::<Vec<i32>>("[1,2,3]") 发生了什么:

几个观察:

1. 两层 Visitor。 外层 SeqVisitor 处理 "我期待一个序列",内层 I32Visitor 处理每个元素的"我期待 i32"。Visitor 递归嵌套对应数据结构的递归嵌套。

2. Deserializer 实例传递。 外层 Deserializer 是整个 JSON;内层反序列化 i32 时,JsonDeserializer 本身也作为(内部子)Deserializer 传给 i32::deserialize。同一个 Deserializer 贯穿多层,但每次方法调用消耗 self。这个"消耗"是逻辑上的(字节流前进),不是物理销毁。

3. None 表示终止。 SeqAccess::next_element 返回 None 而不是错误——终止是正常流程,不是异常。这和 Rust Iterator 的 next() 返回 None 是一个思路。

4. 错误会一路向上传播。 如果 JSON 里某个元素不是数字而是字符串,visit_i32 不会被调用,而是 visit_str 被调用——i32 visitor 没实现 visit_str,默认返回 invalid_type 错误。错误经过 ? 一路返回到 from_str

4.9 Unexpected 与 Expected:结构化错误的基础

前面多次提到 Error::invalid_typeUnexpected 枚举。它们是 Serde 错误系统的核心:

rust
// serde/serde_core/src/de/mod.rs:337
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

rust
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 作为"期待什么"的说明。

所以错误构造变成:

rust
Err(E::invalid_type(Unexpected::Str("hello"), &self))
// 生成: "invalid type: string `hello`, expected a 32-bit integer"

这套结构化错误的价值在于——即使你深度嵌套(struct 里的 enum 里的 Option 里的 i32),错误信息也能准确定位

Error trait 还提供了其他构造函数:invalid_valueinvalid_lengthunknown_variantunknown_fieldmissing_fieldduplicate_field。每一个对应 Serde 反序列化的一类典型错误场景。它们是 反序列化错误的分类法,第 16 章讨论错误处理时会回到这个话题。

4.10 DeserializeOwned 与 'de:何时能借用

前面提到 Deserialize 的生命周期参数 'de。什么时候会有"可以借用的场景"?什么时候只能拥有?

Serde 提供了一个辅助 trait:

rust
// serde/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 表达。

用法区别:

rust
// 可能借用输入数据的反序列化
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 strT = MyStruct { name: &'a str }

from_readerio::Read 流式读取,数据在读取过程中会被丢弃,不存在可借用的长期数据。所以 T 必须是 DeserializeOwned——不能借用任何东西。

实际影响:

rust
#[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);  // 编译错误!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.10.1 实测:serde_core/src/de/ 7698 行 vs serde_core/src/ser/ 3441 行

serde_core 内 de/ 和 ser/ 两个子模块的实测行数并列——

模块文件数主要文件
serde_core/src/de/47698mod.rs 2392(trait 定义)+ impls.rs 3173(内置类型 impl Deserialize)+ value.rs 1895(让原语类型直接当 Deserializer 用)+ ignored_any.rs 238(IgnoredAny 类型)
serde_core/src/ser/43441mod.rs 2010(trait 定义)+ impls.rs 1045(内置类型 impl Serialize)+ impossible.rs 216(Impossible 用于不可能的 SerializeXxx)+ fmt.rs 170(基于 Display 的 serializer)
比例2.24x反序列化代码量 = 序列化的 2.24 倍

两条值得记住的物理事实——

  1. de/impls.rs 3173 行 ≈ de/mod.rs 2392 行 × 1.32——内置类型的反序列化实现(Vec/HashMap/Option/Box/Arc/Cow/BTreeMap/Tuple/Array/...)比 trait 定义本身还多三分之一——因为每个集合类型要为每种 Visitor 调用路径写实现:Vec<T> 既可能从 visit_seq 来(数组形式)也可能从 visit_str 来(CSV 风格、被自定义反序列化器调用);HashMap 既可能从 visit_map 来也可能从 visit_seq 来(key-value pair 列表形式)——多源输入×多目标类型 = 笛卡尔积爆炸——这是 Visitor 模式工程代价的根因
  2. de/value.rs 1895 行——一个独立子系统——它实现 impl Deserializer for &str / impl Deserializer for u32 / impl Deserializer for SeqAccess / ...——让任何 Rust 原语都能"反过来"当 Deserializer 用——这是 §13 testing helpers / §14 Content / §15 borrowed deserialization 等多个高级特性的共同地基;ser 侧没有等价文件——序列化没有"让 i32 当 Serializer 用"的需求

对比 §10.2 实测的 serde_derive/(8969 行)——serde_core 总共 11000+ 行(含 ser + de)+ serde_derive 8969 + proc-macro2 6030(§6.11.1)= 约 26000 行才让 #[derive(Serialize, Deserialize)] 跑起来——印证 §6.11.1 末尾 "基础设施的字面意义"。

4.10.2 serde_json 的入口选择:自描述格式如何调度 Visitor

反序列化最容易误解的一点是:Deserializer 不是简单地"读一个值然后返回"。它要在输入字节、目标类型和 Visitor 之间做一次调度。serde_json 是自描述格式,源码把这件事写得非常直观。

serde_json/src/de.rs:1389-1408&mut Deserializer<R> 实现 de::Deserializerdeserialize_any 的第一步是 parse_whitespace() 后看下一个字节:nvisit_unitt/f 走 bool," 走字符串,[ 走 seq,{ 走 map。也就是说,自描述格式可以先观察输入,再选择 Visitor 方法。

但 Serde 仍然保留类型化入口。serde_json/src/de.rs:1820-1866deserialize_struct 只接受 [{,然后分别调用 visitor.visit_seqvisitor.visit_map;如果下一个字节不是这两类,直接报 invalid_typeserde_json/src/de.rs:1871-1885deserialize_enum 则要求对象形态,因为默认外部 tag 的 JSON 是 {"Variant": ...}serde_json/src/de.rs:1903-1908deserialize_identifier 只是转到 deserialize_str,说明字段名/变体名在 JSON 中本质上是字符串。

这段源码给第 4 章的抽象补上了工程边界:

场景推荐入口serde_json 行为设计含义
serde_json::Valuedeserialize_any根据字节动态分派Value 想保留输入原貌,必须让格式先观察
普通 structdeserialize_struct限定数组或对象目标类型已知,不需要接受任意 JSON
enum 默认表示deserialize_enum期待外部 tag 对象让派生代码拿到 variant 名再进 variant payload
字段名deserialize_identifier转成字符串路径字段识别交给派生出的 __FieldVisitor

非自描述格式会做相反选择。以 bincode 这类格式为例,输入字节里没有"这是字符串还是整数"的显式 tag,必须由目标类型告诉格式下一步读什么。Serde 的 API 同时存在 deserialize_any 和几十个 deserialize_*,就是为了让 JSON 这类格式有动态入口,也让 bincode 这类格式保持类型驱动入口。

serde_json/src/de.rs:2330-2356 还提供 StreamDeserializer,把连续 JSON 值包装成迭代器;serde_json/src/de.rs:2434-2450Iterator::next 每次跳过空白再尝试解析一个 T。这说明 Deserializer 不只是"一次性 from_str" 的内部实现,它也能作为流式协议的游标。第 17 章讲 JSON-RPC、日志流和 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 章会专门展开这个差异;在第 4 章,只要记住一点:Visitor 不是单纯的设计模式,它也是生命周期和所有权协商的载体。

这也是为什么手写 Deserialize 比手写 Serialize 更容易写错。序列化只要把已有值按协议交出去;反序列化要决定输入入口、错误类型、借用关系、缺字段策略和多形态兼容。Visitor 把这些选择集中到一组回调里,复杂,但可审查。

实际审查反序列化代码时,可以先问三个问题:输入格式能否自描述,目标类型是否允许借用,错误应该在格式层还是类型层产生。三个问题答清楚,deserialize_any、类型化入口和 Visitor 方法之间的关系就不会混乱。

所以本章不是在介绍一个设计模式名词,而是在建立反序列化的审查框架。输入由谁解释、值由谁构造、生命周期由谁证明,三者分清,源码里的大量回调才会显得必要。

4.11 本章小结

Deserializer 和 Visitor 是 Serde 反序列化侧的两个核心抽象。它们协同工作的模式叫 Visitor 模式:

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

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

  • 多形式输入&str vs String vs &'de str):Visitor 有三个 visit_* 方法,Deserializer 选最高效的调用。
  • 自描述 vs 非自描述deserialize_any 让自描述格式可以"动态识别",非自描述格式则坚持"提前声明期待"。
  • 借用与所有权'de 生命周期参数把借用关系写进类型系统,DeserializeOwned 是"不借用"的显式标记。

比较序列化与反序列化的对称性:

方面序列化反序列化
入口Serialize::serializeDeserialize::deserialize
格式侧 traitSerializer (39 方法)Deserializer (31 方法)
数据侧 traitSerializeVisitor(间接,由 Deserialize 创建)
状态机7 个 SerializeXxx4 个 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_i32,但如果收到 0 就用 invalid_value 报错。测试三种情况:有效值、零值、字符串输入,观察错误信息的差异。

  2. 自描述 vs 非自描述对比

    rust
    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: 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/serde_core/src/de/mod.rs 的 1000-2000 行:Visitor trait 的完整定义和默认实现,读一遍对"27 个 visit_* 如何相互 fallback" 有完整认识。
  • 丛书卷一《Rust 编译器》第 4 章"生命周期推导":本章 'de 话题的编译器视角补充。

基于 VitePress 构建