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。
收件人(Deserialize)在取件单上写「我等的是一个小件」(deserialize_u8),再把一只手套(Visitor)留在柜台。手套上缝了一排口袋,各标着「信封」「小件」「箱子」(visit_str、visit_u64、visit_seq)。柜员(Deserializer)看包裹,把它塞进对应的口袋。没缝口袋的位置贴着一张纸条:「这不是我要的,我要的是 u8」(默认实现加 expecting)。
比方到这里为止:真实的 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 两个参数的版本。你只列出想转发的那些,其余的自己写。
谁用了它、用在哪些方法上,差别很大:
| Deserializer | deserialize_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 的 Part | visit_borrowed_str 或 visit_string | 字符串类、struct、seq 转发;u8 等先 parse::<u8>() 再交给 visitor |
| bincode 1.3.3 | Err(DeserializeAnyNotSupported) | 不用这个宏,完全照提示读:deserialize_u8 读 1 个字节调 visit_u8 |
| postcard 1.1.3 | Err(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、内部标签,见缓冲主线)用不了非自描述格式的原因。
deserialize_any 原来就叫 deserializeserde 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 后去引号里解析数字(反序列化一侧细讲)。
问: 既然 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_i32 | self.visit_i64(v as i64) |
visit_u8 / visit_u16 / visit_u32 | self.visit_u64(v as u64) |
visit_f32 | self.visit_f64(v as f64) |
visit_char | self.visit_str(v.encode_utf8(&mut [0u8; 4])) |
visit_borrowed_str、visit_string | self.visit_str(...) |
visit_borrowed_bytes、visit_byte_buf | self.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_enum | invalid_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」。
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_placefeature 打开时生成,经InPlaceSeed递归。is_human_readable默认true,bincode / postcard 返回false;serde_core用它让 IP 和 socket 地址在文本里是字符串、在二进制里是字节。