Rust in Everything
基于 serde 1.0.229

Deserialize、Deserializer 与 Visitor

主线位置:反序列化主线的骨架。一次 from_str 里,Deserialize::deserialize 调 deserializer.deserialize_struct(..., __Visitor),格式再回调 __Visitor 的 visit_map。这一页讲这三个 trait 本身:提示是什么、Visitor 的默认实现怎么转发、deserialize_in_place 和 is_human_readable 在反序列化一侧做什么。visit_map 拿到的 MapAccess 怎么用,在下一页 SeqAccess、MapAccess 与 EnumAccess。

sequenceDiagram
  participant T as u8 的 Deserialize
  participant D as 格式 Deserializer
  participant V as PrimitiveVisitor
  T->>D: deserialize_u8(V)
  Note over D: 读输入,自己决定是什么
  D->>V: visit_u64(300)
  V-->>D: Err invalid value
  D-->>T: 同一个 Result

序列化一侧是类型把数据推给格式:Serialize::serialize 知道自己是什么,直接调 serialize_u8。反序列化反过来:类型不知道输入里是什么,只能告诉格式「我想要什么」,再交出一个回调对象,由格式读完输入后挑一个方法调。trait 定义都在 serde_core/src/de/mod.rs。

💡TIP
打个比方:快递柜取件

收件人(Deserialize)在取件单上写「我等的是一个小件」(deserialize_u8),再把一只手套(Visitor)留在柜台。手套上缝了一排口袋,各标着「信封」「小件」「箱子」(visit_str、visit_u64、visit_seq)。柜员(Deserializer)看包裹,把它塞进对应的口袋。没缝口袋的位置贴着一张纸条:「这不是我要的,我要的是 u8」(默认实现加 expecting)。

有的柜台包裹上都贴着面单(JSON),柜员看面单就知道是什么,取件单几乎可以不看。有的柜台只有一卷没有标记的纸带(bincode),柜员只能按取件单上写的长度剪一段下来。

比方到这里为止:真实的 Visitor 按值传进去,只会被调用一次,一个口袋用掉手套就没了;包裹是箱子时,柜员递过去的也不是整箱东西,而是一个可以一件件往外掏的入口(SeqAccess)。

三个 trait 各管什么

trait谁实现做什么
Deserialize<'de>数据类型(u8、String、derive 出来的 Post)选一个 deserialize_* 方法当提示,配上自己的 Visitor
Deserializer<'de>数据格式(&mut serde_json::Deserializer<R>)读输入,按读到的东西调 Visitor 的某个 visit_*
Visitor<'de>数据类型(通常是一个私有的零大小结构体)每个 visit_* 接收一种数据模型里的值,返回 Self::Value

Deserialize 只有一个必需方法:

pub trait Deserialize<'de>: Sized {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>;
    // ...
}

它是关联函数,不是方法:反序列化之前还没有 self。错误类型是 D::Error,由格式决定,serde 只规定怎么构造(见错误)。'de 是输入数据的生命周期,生命周期 'de 与借用专门讲。

Deserializer 有 31 个 deserialize_*。除了 deserialize_any、deserialize_identifier(结构体字段名和 enum 变体名用)、deserialize_ignored_any(跳过不要的值),其余 28 个对应数据模型的 29 种类型:字符串和字节数组各有借用、拥有两个入口(str / string、bytes / byte_buf),四种 variant 共用一个 deserialize_enum。每个方法都把 self 按值拿走、接一个 V: Visitor<'de>、返回 V::Value。返回类型由 Visitor 决定,格式完全不知道最后造出来的是什么类型。serde_json 给 &mut Deserializer<R> 实现这个 trait,所以「按值拿走」拿走的只是一个可变引用,复合类型里可以一层层往下传。

deserialize_* 是提示

trait 文档把两种入口说得很直白:

/// 2. The various `deserialize_*` methods. Non-self-describing formats like
///    Postcard need to be told what is in the input in order to deserialize it.
///    The `deserialize_*` methods are hints to the deserializer for how to
///    interpret the next piece of input. Non-self-describing formats are not
///    able to deserialize something like `serde_json::Value` which relies on
///    `Deserializer::deserialize_any`.

deserialize_any 的意思是「我不知道,你看着办」。JSON 是自描述的:看到 { 就知道是 map,看到 " 就知道是字符串,所以它能实现 deserialize_any,其他方法的提示可以不看。bincode、postcard 的字节流里没有类型标记,2c 01 可以是一个 u16,也可以是两个 u8,只能照提示读。

forward_to_deserialize_any!

对一个只能「看着办」的格式,30 个方法都写成 self.deserialize_any(visitor) 太啰嗦,forward_to_deserialize_any! 替你写。宏的每个名字展开成一个方法,模板是 forward_to_deserialize_any_method!:

macro_rules! forward_to_deserialize_any_method {
    ($func:ident<$l:tt, $v:ident>($($arg:ident : $ty:ty),*)) => {
        #[inline]
        fn $func<$v>(self, $($arg: $ty,)* visitor: $v) -> $crate::__private::Result<$v::Value, <Self as $crate::de::Deserializer<$l>>::Error>
        where
            $v: $crate::de::Visitor<$l>,
        {
            $(
                let _ = $arg;
            )*
            self.deserialize_any(visitor)
        }
    };
}

提示里带的参数(结构体名、字段表、元组长度)被 let _ = $arg; 丢掉。forward_to_deserialize_any_helper! 是一张 30 条规则的表(除 any 外每个方法一条),把 u8 映射到 deserialize_u8<$l, $v>()、把 struct 映射到带 name 和 fields 两个参数的版本。你只列出想转发的那些,其余的自己写。

谁用了它、用在哪些方法上,差别很大:

Deserializerdeserialize_any提示怎么用
&mut serde_json::Deserializer<R>按首字节分派不用这个宏,其余 30 个方法都自己写。i8 到 u64、f32、f64 的提示走同一个 deserialize_number:只检查「是不是数字」,不管宽度
serde_json::Map<String, Value>visit_map除 enum、ignored_any 外全部转发:它只可能是一个 map
serde_json 的 MapKey读一个字符串str、struct 等转发;整数提示由 deserialize_numeric_key! 把引号里的 "1" 当数字解析
serde_urlencoded 0.7.1 的 Partvisit_borrowed_str 或 visit_string字符串类、struct、seq 转发;u8 等先 parse::<u8>() 再交给 visitor
bincode 1.3.3Err(DeserializeAnyNotSupported)不用这个宏,完全照提示读:deserialize_u8 读 1 个字节调 visit_u8
postcard 1.1.3Err(Error::WontImplement)同上

serde_json 的整数提示都由一个小宏 deserialize_number! 生成,deserialize_u8 就是其中一行:

    deserialize_number!(deserialize_i8);
    deserialize_number!(deserialize_i16);
    deserialize_number!(deserialize_i32);
    deserialize_number!(deserialize_i64);
    deserialize_number!(deserialize_u8);
    deserialize_number!(deserialize_u16);
    deserialize_number!(deserialize_u32);
    deserialize_number!(deserialize_u64);

展开都是 self.deserialize_number(visitor)。deserialize_number 看下一个非空白字节:- 或数字就解析,然后调 visit_u64 / visit_i64 / visit_f64 之一;别的字节直接 peek_invalid_type。JSON 文本里的数字没有宽度,解析器也就不去猜宽度,范围检查留给 Visitor。

实验:同一个 u8,三种格式

为了看清调用顺序,实验 crate 里写了一个 LogDe<D>:它包住任意一个 Deserializer,30 多个 deserialize_* 都先打印方法名再转给内层;传给内层的 Visitor 也被包一层,每个 visit_* 打印参数再转给真正的 Visitor。复合类型用到的 SeqAccess、MapAccess、EnumAccess 同样包一层。axum 的 Json 用的 serde_path_to_error 是同一种结构:一个包住格式的 Deserializer,加上包住 Visitor 和各个 Access 的 Wrap(见 axum 卷的 body 提取器)。

src/bin/hints.rs(M2 Max、macOS、rustc 1.99.0):

use bincode::Options;
use exp_serde_de::LogDe;
use serde::Deserialize;

fn json<'a, T: Deserialize<'a> + std::fmt::Debug>(label: &str, input: &'a str) {
  println!("== json {label}: {input}");
  let mut de = serde_json::Deserializer::from_str(input);
  let r = T::deserialize(LogDe::new(&mut de));
  println!("=> {r:?}");
}

fn bin<'a, T: Deserialize<'a> + std::fmt::Debug>(label: &str, input: &'a [u8]) {
  println!("== bincode {label}: {input:?}");
  let opts = bincode::options().with_fixint_encoding().allow_trailing_bytes();
  let mut de = bincode::Deserializer::from_slice(input, opts);
  let r = T::deserialize(LogDe::new(&mut de));
  println!("=> {r:?}");
}

fn main() {
  json::<u8>("u8", "7");
  json::<u8>("u8", "300");
  json::<u8>("u8", "\"7\"");
  json::<u8>("u8", "7.0");
  let bytes = bincode::serialize(&300u16).unwrap();
  bin::<u8>("u8", &bytes);
  bin::<u16>("u16", &bytes);
  json::<serde_json::Value>("Value", "300");
  bin::<serde_json::Value>("Value", &bytes);
}
== json u8: 7
deserialize_u8
  visit_u64(7)
=> Ok(7)
== json u8: 300
deserialize_u8
  visit_u64(300)
=> Err(Error("invalid value: integer `300`, expected u8", line: 1, column: 3))
== json u8: "7"
deserialize_u8
=> Err(Error("invalid type: string \"7\", expected u8", line: 1, column: 3))
== json u8: 7.0
deserialize_u8
  visit_f64(7.0)
=> Err(Error("invalid type: floating point `7.0`, expected u8", line: 1, column: 3))
== bincode u8: [44, 1]
deserialize_u8
  visit_u8(44)
=> Ok(44)
== bincode u16: [44, 1]
deserialize_u16
  visit_u16(300)
=> Ok(300)
== json Value: 300
deserialize_any
  visit_u64(300)
=> Ok(Number(300))
== bincode Value: [44, 1]
deserialize_any
=> Err(DeserializeAnyNotSupported)

逐条看:

  • JSON 收到 deserialize_u8,调的却是 visit_u64。u8 的 Visitor 在 visit_u64 里检查范围,300 超出,报 invalid value(值的类型对,值不对)。范围检查的代码在标准库类型的实现。
  • "7" 一个 visit_* 都没调:deserialize_number 看到 " 就在格式里报错了,invalid type。
  • 7.0 调的是 visit_f64:serde_json 的 deserialize_number 照样解析浮点,交给 visitor 去拒绝。
  • bincode 的 300u16 是小端的 [44, 1]。按 u8 读,它只取第一个字节,得到 44,没有任何报错(allow_trailing_bytes 让剩下的那个字节不算错)。同样两个字节按 u16 读是 300。字节流本身说不出自己是什么,读对读错全靠提示。
  • serde_json::Value 的 Deserialize 用 deserialize_any。JSON 能回答,bincode 不能,这就是 Value(以及依赖它的 untagged、内部标签,见缓冲主线)用不了非自描述格式的原因。
📝NOTE
冷知识:deserialize_any 原来就叫 deserialize

serde 0.9 里这个方法叫 Deserializer::deserialize。issue #874 提议改名,理由有三条,第一条是「I think the current method is overused unintentionally and I think a better name would clarify the point of the method.」;第二条是两个 trait 都有叫 deserialize 的方法,同时在作用域里时「serde_json::Value::deserialize is ambiguous」;第三条是新名字能和 deserialize_ignored_any 对上。PR #878 改名,随 1.0.0 发布,发布说明写的是:「The old name did nothing to clarify the purpose of this method in contrast to any of the other Deserializer trait methods.」

新名字带一个 _any,读起来就是「上面那张表里的任何一种都行」。deserialize_any 有代价(非自描述格式不支持),名字把这一点露在了调用处。

提示也能补充信息

在 JSON 主解析器里,数字提示只用来确认「是不是数字」。到了只有字符串的地方,提示就成了唯一的类型信息。serde_urlencoded 里所有值都是字符串,n=7 读成 u8 全靠 deserialize_u8。Logged<T> 是实验 crate 里的一个字段包装:它的 Deserialize 就是 T::deserialize(LogDe::new(d)),用来只看某一个字段的调用。src/bin/urlenc.rs:

use exp_serde_de::Logged;
use serde::Deserialize;

#[derive(Deserialize, Debug)]
#[allow(dead_code)]
struct Q {
  n: Logged<u8>,
}

fn main() {
  for input in ["n=7", "n=300", "n=abc"] {
    println!("== urlencoded {input}");
    println!("=> {:?}", serde_urlencoded::from_str::<Q>(input));
  }
  println!("== json {{\"n\":\"7\"}}");
  println!("=> {:?}", serde_json::from_str::<Q>(r#"{"n":"7"}"#));
}
== urlencoded n=7
deserialize_u8
  visit_u8(7)
=> Ok(Q { n: Logged(7) })
== urlencoded n=300
deserialize_u8
=> Err(Error("number too large to fit in target type"))
== urlencoded n=abc
deserialize_u8
=> Err(Error("invalid digit found in string"))
== json {"n":"7"}
deserialize_u8
=> Err(Error("invalid type: string \"7\", expected u8", line: 1, column: 8))

serde_urlencoded 用 str::parse::<u8> 解析,报错就是标准库 ParseIntError 的原文;解析成功后经 7u8.into_deserializer() 调 visit_u8。同一个字符串 "7",JSON 拒绝、urlencoded 接受:提示是一样的,格式对它的理解不同。serde_json 里 map 的键也是这样,HashMap<u32, _> 能从 {"1": ...} 读出来,靠的是 MapKey 收到 deserialize_u32 后去引号里解析数字(反序列化一侧细讲)。

📝NOTE
没有蠢问题

问: 既然 serde_json 的 deserialize_u8 最后调的是 visit_u64,它为什么不干脆把整数提示也转给 deserialize_any?

答: 默认 feature 下,对数字两者确实走同一个 parse_integer;对 "7" 这类非数字,deserialize_any 会让 u8 的 Visitor 用默认实现拒绝,deserialize_number 则由 peek_invalid_type 在格式里拒绝,两条路报的都是 invalid type。差别出在 arbitrary_precision feature 下:deserialize_any 用的 parse_any_number 先把数字文本收进一个 String,放不进 u64 / i64 的数(比如 1.5)会以 map 的形式交给 Visitor;照提示走的 deserialize_f64 还是普通的 visit_f64。在另一个打开了这个 feature 的 crate 里跑:

  println!("== f64::deserialize on 1.5");
  let mut de = serde_json::Deserializer::from_str("1.5");
  println!("=> {:?}", f64::deserialize(LogDe::new(&mut de)));
  println!("== deserialize_any on 1.5 (IgnoredAny visitor)");
  let mut de = serde_json::Deserializer::from_str("1.5");
  println!("=> {:?}", LogDe::new(&mut de).deserialize_any(IgnoredAny));
== f64::deserialize on 1.5
deserialize_f64
  visit_f64(1.5)
=> Ok(1.5)
== deserialize_any on 1.5 (IgnoredAny visitor)
deserialize_any
  visit_map
    next_entry_seed
      deserialize_ignored_any
        visit_borrowed_str("$serde_json::private::Number")
      deserialize_ignored_any
        visit_string("1.5")
    -> Some
    next_entry_seed
    -> None
=> Ok(IgnoredAny)

提示让格式知道「这里要的是 f64」,可以不绕这一圈。MapKey 更离不开提示:在那里转给 deserialize_any 会把 "1" 当字符串,u32 键就读不出来了。

两个有默认实现的提示

31 个 deserialize_* 里只有两个有默认实现,deserialize_i128 和 deserialize_u128:

    fn deserialize_i128<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>,
    {
        let _ = visitor;
        Err(Error::custom("i128 is not supported"))
    }

文档只有一句「The default behavior unconditionally returns an error.」。这两个方法是 1.0.60 才加进 trait 的(那一版的发布说明:「Add Serde impls for i128 and u128 when compiling with Rust 1.26+」),比其他方法晚,给了默认实现,不支持 128 位整数的格式就可以不写。serde_json 实现了这两个方法(do_deserialize_i128,先把数字的文本收进一个 String 再解析)。

trait 里还有一个 #[doc(hidden)] 的 __deserialize_content_v1,注释写着「Not public API.」,默认就是 self.deserialize_any(visitor)。它给缓冲主线用,见 Content:缓冲的代价。

Visitor:默认全部拒绝

Visitor 只有两样东西是必需的:关联类型 Value 和 expecting。其余 27 个 visit_* 都有默认实现,要么转给另一个 visit_*,要么报 invalid_type。visit_bool 是最典型的一个:

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

&self 被当成 &dyn Expected 传进去:每个 Visitor 都自动实现了 Expected,实现就是调 expecting。于是报错的后半句「expected u8」来自 u8 那个 Visitor 的 expecting,前半句「invalid type: boolean true」来自 Unexpected::Bool。两半怎么拼、serde_json 怎么把 Unexpected::Unit 写成 null,在错误。

所以写一个 Visitor 只需要实现你接受的那几种输入。没实现的都按「类型不对」处理,错误信息自动带上你写的 expecting。

默认转发链

不全是直接报错。有一部分默认实现会把值换个类型转给另一个 visit_*,例如 visit_i8 和 visit_borrowed_str:

    fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
    where
        E: Error,
    {
        self.visit_i64(v as i64)
    }
    #[inline]
    fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
    where
        E: Error,
    {
        self.visit_str(v)
    }

全部默认实现整理成表:

方法默认实现
visit_i8 / visit_i16 / visit_i32self.visit_i64(v as i64)
visit_u8 / visit_u16 / visit_u32self.visit_u64(v as u64)
visit_f32self.visit_f64(v as f64)
visit_charself.visit_str(v.encode_utf8(&mut [0u8; 4]))
visit_borrowed_str、visit_stringself.visit_str(...)
visit_borrowed_bytes、visit_byte_bufself.visit_bytes(...)
visit_i128 / visit_u128不转发,直接 invalid_type,Unexpected::Other("integer `5` as i128")
visit_bool、visit_i64、visit_u64、visit_f64、visit_str、visit_bytes、visit_none、visit_some、visit_unit、visit_newtype_struct、visit_seq、visit_map、visit_enuminvalid_type

这个形状有两个用意。一是格式可以调最精确的方法(bincode 调 visit_u8,serde_json 调 visit_borrowed_str),而 Visitor 只实现最宽的那个(visit_u64、visit_str)就能全接住。二是借用和所有权的三种字符串(visit_borrowed_str 借输入、visit_str 临时借、visit_string 给所有权)默认都汇到 visit_str:不在乎借用的 Visitor 只写一个 visit_str;&'de str 的 Visitor 只实现 visit_borrowed_str,其余两种落到 visit_str 的默认实现报错(生命周期 'de 与借用)。

128 位整数不往 64 位转:i128 往 i64 转会截断。它的报错文本要先格式化:visit_i128 不像 visit_string 那样带 #[cfg(any(feature = "std", feature = "alloc"))],不能用 String,于是在栈上开一个 58 字节的缓冲([0u8; 58]),用 crate::format::Buf 写进去(visit_u128 是 57 字节)。

实验:只实现五个方法的 Visitor

src/bin/chain.rs:Five 只实现 visit_i64、visit_u64、visit_f64、visit_str、visit_bytes,每个都返回一个说明自己被调用了的字符串。直接调各个 visit_*,看默认实现最后落到哪里;再交给 serde_json 的 deserialize_any:

use serde::de::{value::Error, Error as _, Visitor};
use std::fmt;

// Implements only five visit_* methods; everything else is the trait default.
struct Five;

impl<'de> Visitor<'de> for Five {
  type Value = String;

  fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
    f.write_str("something Five accepts")
  }
  fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<String, E> {
    Ok(format!("visit_i64({v})"))
  }
  fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<String, E> {
    Ok(format!("visit_u64({v})"))
  }
  fn visit_f64<E: serde::de::Error>(self, v: f64) -> Result<String, E> {
    Ok(format!("visit_f64({v})"))
  }
  fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<String, E> {
    Ok(format!("visit_str({v:?})"))
  }
  fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<String, E> {
    Ok(format!("visit_bytes({v:?})"))
  }
}

fn show(called: &str, r: Result<String, Error>) {
  match r {
    Ok(s) => println!("{called:<28} -> {s}"),
    Err(e) => println!("{called:<28} -> Err: {e}"),
  }
}

fn main() {
  show("visit_i8(-5)", Five.visit_i8(-5));
  show("visit_i16(-5)", Five.visit_i16(-5));
  // ... 其余每个 visit_* 一行,见下面的输出
  println!("---- through serde_json ----");
  for input in ["true", "null", "[1]", "{}", "-1", "1e3", "\"a\\nb\"", "\"ab\""] {
    let mut de = serde_json::Deserializer::from_str(input);
    let r = serde::Deserializer::deserialize_any(&mut de, Five);
    println!("{input:<10} -> {r:?}");
  }
}

visit_i128 和 visit_u128 两行的输出放在本页末尾的「动脑筋答案」里,先看其余的:

visit_i8(-5)                 -> visit_i64(-5)
visit_i16(-5)                -> visit_i64(-5)
visit_i32(-5)                -> visit_i64(-5)
visit_u8(5)                  -> visit_u64(5)
visit_u16(5)                 -> visit_u64(5)
visit_u32(5)                 -> visit_u64(5)
visit_f32(1.5)               -> visit_f64(1.5)
visit_char('x')              -> visit_str("x")
visit_borrowed_str("b")      -> visit_str("b")
visit_string("s")            -> visit_str("s")
visit_borrowed_bytes(b"b")   -> visit_bytes([98])
visit_byte_buf(vec![1])      -> visit_bytes([1])
visit_bool(true)             -> Err: invalid type: boolean `true`, expected something Five accepts
visit_none()                 -> Err: invalid type: Option value, expected something Five accepts
visit_unit()                 -> Err: invalid type: unit value, expected something Five accepts
---- through serde_json ----
true       -> Err(Error("invalid type: boolean `true`, expected something Five accepts", line: 1, column: 4))
null       -> Err(Error("invalid type: null, expected something Five accepts", line: 1, column: 4))
[1]        -> Err(Error("invalid type: sequence, expected something Five accepts", line: 1, column: 1))
{}         -> Err(Error("invalid type: map, expected something Five accepts", line: 1, column: 2))
-1         -> Ok("visit_i64(-1)")
1e3        -> Ok("visit_f64(1000)")
"a\nb"     -> Ok("visit_str(\"a\\nb\")")
"ab"       -> Ok("visit_str(\"ab\")")

JSON 的两个字符串走的其实是两个方法。用 LogDe 包一层读成 String(src/bin/strs.rs):

  for input in [r#""ab""#, r#""a\nb""#] {
    println!("== json {input}");
    let mut de = serde_json::Deserializer::from_str(input);
    println!("=> {:?}", String::deserialize(LogDe::new(&mut de)));
  }
== json "ab"
deserialize_string
  visit_borrowed_str("ab")
=> Ok("ab")
== json "a\nb"
deserialize_string
  visit_str("a\nb")
=> Ok("a\nb")

没有转义的字符串直接借输入;带转义的要先解码到暂存区,只能临时借出。Five 两条路都接住了,因为 visit_borrowed_str 默认转给 visit_str。null 对应 visit_unit,同一个 Unexpected::Unit 在 serde::de::value::Error 下写作「unit value」,在 serde_json 下写作「null」。

❗IMPORTANT
动脑筋:Five.visit_u128(5) 返回什么?

平台 M2 Max、macOS、rustc 1.99.0,代码就是上面的 chain.rs,main 里那两行是:

  show("visit_i128(5)", Five.visit_i128(5));
  show("visit_u128(5)", Five.visit_u128(5));

Five 实现了 visit_u64。visit_u128(5) 会落到 visit_u64(5) 吗?如果不会,打印出来的那一行是什么?

Visitor 最后还有一个 #[doc(hidden)] 的 __private_visit_untagged_option,注释是「Used when deserializing a flattened Option field. Not public API.」,默认返回 Err(())。它属于 flatten 的实现细节,手写 Visitor 不用管。

deserialize_in_place:往已有的值里读

Deserialize 还有一个隐藏方法 deserialize_in_place:

    /// This method is stable and an official public API, but hidden from the
    /// documentation because it is almost never what newbies are looking for.
    /// Showing it in rustdoc would cause it to be featured more prominently
    /// than it deserves.
    #[doc(hidden)]
    fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
    where
        D: Deserializer<'de>,
    {
        // Default implementation just delegates to `deserialize` impl.
        *place = tri!(Deserialize::deserialize(deserializer));
        Ok(())
    }

目的写在文档第一段:「allow the deserializer to reuse resources and avoid copies」。出错时 place 可能只被改了一半,文档说这种状态「indeterminate」但内存安全。默认实现就是新建一个值再整个赋过去,什么也没复用。

真正复用的是 serde_core 里覆盖了它的几个 impl:String、Vec<T>、其他序列集合、数组和元组。String 的版本换了一个 Visitor,StringInPlaceVisitor 在 visit_str 里 clear() 再 push_str(v),原来的缓冲留着。Vec<T> 的 deserialize_in_place对已有的每个元素再调一次 deserialize_in_place,元素少了就 truncate,多了就 push:

                for i in 0..self.0.len() {
                    let next = {
                        let next_place = InPlaceSeed(&mut self.0[i]);
                        tri!(seq.next_element_seed(next_place))
                    };
                    if next.is_none() {
                        self.0.truncate(i);
                        return Ok(());
                    }
                }

                while let Some(value) = tri!(seq.next_element()) {
                    self.0.push(value);
                }

「对一个已有元素原地反序列化」要通过 SeqAccess::next_element_seed 传进去,于是需要一个 seed。这个 seed 是 InPlaceSeed,deserialize 里就一行 T::deserialize_in_place(deserializer, self.0)。seed 是什么在下一页讲。

谁来调 deserialize_in_place?serde_json 的 from_str 系列不调,它们都走 T::deserialize。调用方只有两类:你自己(手里有一个想复用的值),以及 serde 自己的原地实现(上面这些 impl,和打开 feature 后 derive 生成的代码)对元素、字段的递归调用。

derive 默认不生成它。serde_derive 的 Cargo.toml 里有一个默认关闭的 feature deserialize_in_place = [],deserialize_in_place_body 只在打开时才产出代码,而且对 enum、unit struct、transparent、from / try_from、identifier、所有字段都 deserialize_with 的类型返回 None,这些类型仍用默认实现。打开后,生成的 __Visitor 持有 place: &'place mut P,每个字段用 InPlaceSeed(&mut self.place.name) 去读,细节见生成的 Deserialize。

实验:复用了没有

src/bin/inplace.rs 用普通的 serde = { features = ["derive"] }:

use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct P {
  name: String,
}

fn ptrs(v: &[String]) -> Vec<*const u8> {
  v.iter().map(|s| s.as_ptr()).collect()
}

fn main() {
  let mut v: Vec<String> = vec![String::with_capacity(32), String::with_capacity(32), String::with_capacity(32)];
  let (vp, sp) = (v.as_ptr(), ptrs(&v));
  let mut de = serde_json::Deserializer::from_str(r#"["x", "y"]"#);
  Vec::<String>::deserialize_in_place(&mut de, &mut v).unwrap();
  println!("Vec<String> in place: {v:?}");
  println!("  vec buffer reused: {}", v.as_ptr() == vp);
  println!("  string buffers reused: {:?}", ptrs(&v).iter().zip(&sp).map(|(a, b)| a == b).collect::<Vec<_>>());
  println!("  capacities: {:?}", v.iter().map(|s| s.capacity()).collect::<Vec<_>>());

  let mut p = P { name: String::with_capacity(32) };
  let before = p.name.as_ptr();
  let mut de = serde_json::Deserializer::from_str(r#"{"name": "z"}"#);
  P::deserialize_in_place(&mut de, &mut p).unwrap();
  println!("derived P in place: {p:?}");
  println!("  name buffer reused: {}, capacity: {}", p.name.as_ptr() == before, p.name.capacity());
}
Vec<String> in place: ["x", "y"]
  vec buffer reused: true
  string buffers reused: [true, true]
  capacities: [32, 32]
derived P in place: P { name: "z" }
  name buffer reused: false, capacity: 1

Vec<String> 的外层缓冲和两个 String 的缓冲都留下了,第三个元素被 truncate 掉。derive 出来的 P 走的是默认实现:cargo expand 出来的 impl Deserialize for P 里只有 fn deserialize,name 是新分配的、容量 1 的 String。

同样的 P 加上 tags: Vec<String>,换到另一个 crate 里打开 serde_derive 的 deserialize_in_place feature(feature 会被统一,所以单独放一个 crate),src/bin/inplace_derive.rs:

use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct P {
  name: String,
  tags: Vec<String>,
}

fn main() {
  let mut p = P { name: String::with_capacity(32), tags: vec![String::with_capacity(32)] };
  let (np, tp) = (p.name.as_ptr(), p.tags[0].as_ptr());
  let mut de = serde_json::Deserializer::from_str(r#"{"name": "z", "tags": ["t"]}"#);
  P::deserialize_in_place(&mut de, &mut p).unwrap();
  println!("derived P in place (feature on): {p:?}");
  println!("  name reused: {}, cap {}", p.name.as_ptr() == np, p.name.capacity());
  println!("  tags[0] reused: {}, cap {}", p.tags[0].as_ptr() == tp, p.tags[0].capacity());
}
derived P in place (feature on): P { name: "z", tags: ["t"] }
  name reused: true, cap 32
  tags[0] reused: true, cap 32

展开后生成代码里 visit_map 的每个字段分支是这样的:

                                _serde::de::MapAccess::next_value_seed(
                                    &mut __map,
                                    _serde::__private229::de::InPlaceSeed(&mut self.place.name),
                                )?;

is_human_readable:同一个类型,两种表示

is_human_readable 默认返回 true,bincode 和 postcard 都覆盖成 false:

    #[inline]
    fn is_human_readable(&self) -> bool {
        true
    }

它让一个类型对文本格式和二进制格式用不同的表示。文档里的警告值得记住:「modifying this method to change a format from human-readable to compact or vice versa should be regarded as a breaking change」,因为按一种模式写出来的数据,换一种模式不一定读得回来。序列化一侧有同名方法,两边必须一致(Serialize 与 Serializer)。

serde_core 自己用它的地方都在网络地址上。Ipv4Addr 和 Ipv6Addr 由 parse_ip_impl! 生成:

                if deserializer.is_human_readable() {
                    deserializer.deserialize_str(FromStrVisitor::new($expecting))
                } else {
                    <[u8; $size]>::deserialize(deserializer).map(<$ty>::from)
                }

IpAddr、SocketAddr、SocketAddrV4、SocketAddrV6 同理:文本格式里是 "127.0.0.1" 这样的字符串,二进制格式里是字节数组、元组或 enum。src/bin/human.rs:

use bincode::Options;
use exp_serde_de::LogDe;
use serde::Deserialize;
use std::net::Ipv4Addr;

fn main() {
  for input in [r#""127.0.0.1""#, "[127,0,0,1]"] {
    println!("== json {input}");
    let mut de = serde_json::Deserializer::from_str(input);
    println!("=> {:?}", Ipv4Addr::deserialize(LogDe::new(&mut de)));
  }
  let bytes = bincode::serialize(&Ipv4Addr::new(127, 0, 0, 1)).unwrap();
  println!("== bincode {bytes:?}");
  let opts = bincode::options().with_fixint_encoding().allow_trailing_bytes();
  let mut de = bincode::Deserializer::from_slice(&bytes, opts);
  println!("=> {:?}", Ipv4Addr::deserialize(LogDe::new(&mut de)));
}
== json "127.0.0.1"
is_human_readable() = true
deserialize_str
  visit_borrowed_str("127.0.0.1")
=> Ok(127.0.0.1)
== json [127,0,0,1]
is_human_readable() = true
deserialize_str
=> Err(Error("invalid type: sequence, expected IPv4 address", line: 1, column: 0))
== bincode [127, 0, 0, 1]
is_human_readable() = false
deserialize_tuple(4)
  visit_seq
    next_element_seed
      deserialize_u8
        visit_u8(127)
    -> Some
    next_element_seed
      deserialize_u8
        visit_u8(0)
    -> Some
    next_element_seed
      deserialize_u8
        visit_u8(0)
    -> Some
    next_element_seed
      deserialize_u8
        visit_u8(1)
    -> Some
=> Ok(127.0.0.1)

在 JSON 里,[127,0,0,1] 是合法 JSON,却读不出 Ipv4Addr:类型先问了 is_human_readable,得到 true,就只接受字符串。在 bincode 里 4 个字节按 [u8; 4] 读,[u8; 4] 走的是 deserialize_tuple(4),数组怎么经 SeqAccess 一个个取元素见下一页。

动脑筋答案

visit_i128(5)                -> Err: invalid type: integer `5` as i128, expected something Five accepts
visit_u128(5)                -> Err: invalid type: integer `5` as u128, expected something Five accepts

不会落到 visit_u64。visit_u128 的默认实现不转发,它把 integer `5` as u128 写进栈上的缓冲,包成 Unexpected::Other 直接报 invalid_type;后半句照样来自 Five::expecting。想接 128 位整数的 Visitor 必须自己实现 visit_i128 / visit_u128,标准库里的 i128 / u128 就是这么做的。小整数向上转发不会丢信息,128 位往 64 位转会丢,所以链条在这里断开。

小结

  • 控制权在格式手里:Deserialize 选一个 deserialize_* 提示、交一个 Visitor,格式读完输入决定调哪个 visit_*,返回类型由 Visitor::Value 决定。
  • 提示的分量因格式而异:JSON 的 deserialize_any 什么都能答,数字提示只检查类别不管宽度;bincode、postcard 的 deserialize_any 直接报错,deserialize_u8 就是「读 1 个字节」;urlencoded 和 JSON 的 map 键要靠提示把字符串解析成数字。
  • forward_to_deserialize_any! 把列出的方法都展开成 self.deserialize_any(visitor),丢掉名字、长度、字段表。
  • Visitor 只需 expecting。小整数转到 visit_i64 / visit_u64,f32 转到 f64,char、借用和拥有的字符串转到 visit_str,字节同理;visit_i128 / visit_u128 不转发;其余直接 invalid_type,后半句是 expecting。
  • deserialize_in_place 默认是 *place = deserialize(..)?;String、Vec 等覆盖了它来复用缓冲。derive 只在 serde_derive 的 deserialize_in_place feature 打开时生成,经 InPlaceSeed 递归。
  • is_human_readable 默认 true,bincode / postcard 返回 false;serde_core 用它让 IP 和 socket 地址在文本里是字符串、在二进制里是字节。

💬 关联讨论

发起讨论
加载中...