标准库类型的实现
主线位置:两条主线的叶子。序列化主线上,
serialize_field("tags", &self.tags)最后落到Vec<String>的Serialize,它调collect_seq;反序列化主线上,next_value::<u8>()最后落到u8的Deserialize,它调deserialize_u8并递进一个Visitor。这一页看这些现成的 impl 各自选了数据模型里的哪种类型、Visitor收哪些visit_*、在哪里检查输入。
flowchart TB json["JSON 300"] --> hint["deserialize_u8 提示"] hint --> parse[serde_json 解析整数] parse --> visit["visit_u64(300)"] visit --> pv[u8 的 PrimitiveVisitor] pv --> conv["u8::try_from"] conv -->|成功| ok["Ok(u8)"] conv -->|失败| err[invalid_value]
代码在四个文件里:
| 文件 | 行数 | 内容 |
|---|---|---|
ser/impls.rs | 1045 | 所有 Serialize impl |
de/impls.rs | 3173 | 所有 Deserialize impl,和它们的 Visitor |
de/ignored_any.rs | 238 | IgnoredAny |
private/size_hint.rs | 30 | from_bounds、cautious |
Deserialize 一侧比 Serialize 长三倍,原因在控制权反转:序列化只需选一个 serialize_* 调一次,反序列化要写一个 Visitor,列出这个类型愿意接受的每一种输入。下面挑几类看。哪些 impl 要开哪个 feature(std、alloc、rc、result)在源码地图。本页的实验都在 M2 Max、macOS、rustc 1.99.0 上跑,依赖锁 serde =1.0.229(derive、rc)和 serde_json =1.0.154。
整数:每对类型之间都写了一个 visit_*
序列化很直接,primitive_impl! 让每个整数调同名的 serialize_*。只有两个例外:
primitive_impl!(isize, serialize_i64 as i64);
// ...
primitive_impl!(usize, serialize_u64 as u64);
数据模型里没有 isize / usize。在 32 位和 64 位平台上写出来的数据是同一种类型,换平台读回来不会对不上。
反序列化要处理的是「格式给的整数类型和目标类型不一样」。serde_json 解析整数时只产生三种值:非负整数走 visit_u64,负数走 visit_i64,带小数点或指数的走 visit_f64(ParserNumber::visit)。目标是 u8 时,u8 的 Visitor 就得能接 u64、i64,并检查范围。这些 Visitor 全部由一个宏 impl_deserialize_num! 生成,u8 的那一次调用:
impl_deserialize_num! {
u8, NonZeroU8, deserialize_u8
num_self!(u8:visit_u8);
int_to_uint!(i8:visit_i8 i16:visit_i16 i32:visit_i32 i64:visit_i64);
uint_to_self!(u16:visit_u16 u32:visit_u32 u64:visit_u64);
}
第一行是目标类型、对应的 NonZero 类型、给格式的提示 deserialize_u8;后面每一行把一组 visit_* 交给一种转换规则。规则一共五种:
| 宏 | 用在 | 做什么 |
|---|---|---|
num_self! | 同类型 | Ok(v) |
num_as_self! | 一定装得下(u8 到 u16、整数到浮点) | Ok(v as Self::Value) |
int_to_int! | 有符号到更窄的有符号 | try_from(v as i64),失败报 Unexpected::Signed |
int_to_uint! | 有符号到无符号 | 先查 0 <= v,再 try_from(v as u64) |
uint_to_self! | 无符号到任何整数 | try_from(v as u64),失败报 Unexpected::Unsigned |
uint_to_self! 的普通分支:
macro_rules! uint_to_self {
($ty:ident : $visit:ident) => {
#[inline]
fn $visit<E>(self, v: $ty) -> Result<Self::Value, E>
where
E: Error,
{
Self::Value::try_from(v as u64)
.map_err(|_| Error::invalid_value(Unexpected::Unsigned(v as u64), &self))
}
};
// ...
}
报错用 invalid_value 而不是 invalid_type:类型对(是整数),值不对。&self 是 Visitor 自己,它的 expecting 写的是 stringify!($primitive),所以报错尾巴是 expected u8。浮点没有出现在 u8 的列表里,visit_f64 用的是 Visitor 的默认实现,报 invalid_type。这两个函数怎么拼出报错原文,见错误。
用 serde_json 读几种输入(src/bin/ints.rs):
use std::num::{NonZeroU8, Saturating, Wrapping};
fn show<T: serde::de::DeserializeOwned + std::fmt::Debug>(ty: &str, input: &str) {
match serde_json::from_str::<T>(input) {
Ok(v) => println!("{ty:<14} <- {input:<6} Ok({v:?})"),
Err(e) => println!("{ty:<14} <- {input:<6} Err({e})"),
}
}
fn main() {
for input in ["255", "300", "-1", "1.0", "1e2", "\"1\""] {
show::<u8>("u8", input);
}
show::<i8>("i8", "200");
show::<u64>("u64", "-1");
show::<f32>("f32", "300");
show::<NonZeroU8>("NonZeroU8", "0");
show::<NonZeroU8>("NonZeroU8", "300");
show::<Saturating<u8>>("Saturating<u8>", "300");
show::<Saturating<u8>>("Saturating<u8>", "-1");
show::<Wrapping<u8>>("Wrapping<u8>", "300");
show::<u128>("u128", "340282366920938463463374607431768211455");
show::<char>("char", "\"ab\"");
}
u8 <- 255 Ok(255)
u8 <- 300 Err(invalid value: integer `300`, expected u8 at line 1 column 3)
u8 <- -1 Err(invalid value: integer `-1`, expected u8 at line 1 column 2)
u8 <- 1.0 Err(invalid type: floating point `1.0`, expected u8 at line 1 column 3)
u8 <- 1e2 Err(invalid type: floating point `100.0`, expected u8 at line 1 column 3)
u8 <- "1" Err(invalid type: string "1", expected u8 at line 1 column 3)
i8 <- 200 Err(invalid value: integer `200`, expected i8 at line 1 column 3)
u64 <- -1 Err(invalid value: integer `-1`, expected u64 at line 1 column 2)
f32 <- 300 Ok(300.0)
NonZeroU8 <- 0 Err(invalid value: integer `0`, expected a nonzero u8 at line 1 column 1)
NonZeroU8 <- 300 Err(invalid value: integer `300`, expected a nonzero u8 at line 1 column 3)
Saturating<u8> <- 300 Ok(255)
Saturating<u8> <- -1 Ok(0)
Wrapping<u8> <- 300 Err(invalid value: integer `300`, expected u8 at line 1 column 3)
u128 <- 340282366920938463463374607431768211455 Ok(340282366920938463463374607431768211455)
char <- "ab" Err(invalid value: string "ab", expected a character at line 1 column 4)
300走visit_u64→uint_to_self!,-1走visit_i64→int_to_uint!:都是invalid value。1.0和1e2在 JSON 里都是浮点(1e2打印成100.0),值是整数也不行:invalid type。- 字符串
"1"不会被解析成数字。要接受这种输入,见常见坑。 f32收300:整数到浮点用num_as_self!,不检查精度。NonZeroU8和Saturating<u8>是同一个宏的另外两个分支(nonzero/saturating)生成的:前者多查一次 0,expecting是a nonzero u8;后者越界时取MIN/MAX,不报错。Wrapping<u8>不走这个宏,它的Deserialize只是Deserialize::deserialize(deserializer).map(Wrapping),报错和u8完全一样,不会回绕。char收一个只含一个字符的字符串(CharVisitor::visit_str取前两个chars()比较),"ab"是invalid value。
128 位整数单独用 num_128!,只出现在 i128 和 u128 的调用里,按 i128 / u128 比较 MIN / MAX。反过来,u8 到 u64 的列表里都没有 visit_i128 / visit_u128。
u8::deserialize(5u128.into_deserializer()) 的结果是什么?平台 M2 Max、rustc 1.99.0。into_deserializer() 把一个 Rust 值包成一个只会调一次 visit_* 的小 Deserializer(见 value 模块),5u128 包出来的那个调 visit_u128(5):
use serde::Deserialize;
use serde::de::IntoDeserializer;
use serde::de::value::Error;
fn main() {
let small = 5u128;
println!("u8 <- 5u128 : {:?}", u8::deserialize(IntoDeserializer::<Error>::into_deserializer(small)));
println!("u64 <- 5u128 : {:?}", u64::deserialize(IntoDeserializer::<Error>::into_deserializer(small)));
println!("i128 <- 5u128 : {:?}", i128::deserialize(IntoDeserializer::<Error>::into_deserializer(small)));
println!("u128 <- 5u8 : {:?}", u128::deserialize(IntoDeserializer::<Error>::into_deserializer(5u8)));
}
先猜第一行是 Ok(5) 还是一个错误,是错误的话是 invalid value 还是 invalid type。
字符串:String 收什么,&str 收什么
str 和 String 的 Serialize 都是 serializer.serialize_str(self)。反序列化分两个 Visitor。
StringVisitor 收四种:visit_str(to_owned() 复制一份)、visit_string(直接拿走格式给的 String,不复制)、visit_bytes 和 visit_byte_buf(先查 UTF-8,不是就报 invalid_value(Unexpected::Bytes(..)))。visit_borrowed_str 没写,默认实现转给 visit_str。String 还实现了 deserialize_in_place,用 StringInPlaceVisitor 先 clear() 再 push_str,复用原来的缓冲。
StrVisitor 是 Visitor<'a>,Value = &'a str,只写了 visit_borrowed_str 和 visit_borrowed_bytes。格式交来一个临时的 &str(比如 serde_json 处理过转义、放在自己缓冲里的字符串),它就报 expected a borrowed string。什么时候能借、什么时候借不了,在生命周期 'de 与借用。Cow<'a, T> 的 Deserialize 一律反序列化成 T::Owned 再包 Cow::Owned,不借,要借得加 #[serde(borrow)],也在那一页。
Option:交给格式判断
Option<T> 的 Serialize 是 serialize_some(value) 或 serialize_none()。Deserialize 调的是 deserialize_option,这是数据模型里专门为 Option 留的一个方法:只有格式知道「这里没有值」怎么表示。OptionVisitor:
#[inline]
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: Error,
{
Ok(None)
}
#[inline]
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: Error,
{
Ok(None)
}
#[inline]
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
T::deserialize(deserializer).map(Some)
}
visit_some 收到的不是值,而是一个 Deserializer:T 拿它接着读。serde_json 的 deserialize_option 看下一个字节,是 n 就吃掉 null、调 visit_none,否则 visit_some(self)。于是 JSON 表示不出 Some(None):
Option<Option<u8>> <- null : None
Some(None::<u8>) -> null
外层 Option 先看到 null,就是 None,内层没有机会读。OptionVisitor 还有一个私有的 __private_visit_untagged_option(T::deserialize(deserializer).ok(),失败就当 None),调它的只有 flatten 用的 FlatMapDeserializer::deserialize_option,见 flatten。结构体缺了一个 Option 字段时得到 None,也走这个 impl:derive 生成的代码对缺失字段调 _serde::__private229::de::missing_field("..."),它用一个只在 deserialize_option 里回 visit_none、其余一律报 missing field 的小 Deserializer 再读一次,见生成的 Deserialize。
Vec 与 size_hint::cautious
Vec、VecDeque、HashSet 等序列的 Serialize 都是一行 serializer.collect_seq(self)(seq_impl!),collect_seq 的默认实现见 Serialize 与 Serializer。
反序列化时,格式在 visit_seq 里交来的 SeqAccess 有一个 size_hint()。bincode 这类格式在序列前面写了长度,能给出准确的 Some(n);serde_json 不知道数组有多长,给 None。Vec<T> 的 Deserialize 用这个提示预分配:
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let capacity = size_hint::cautious::<T>(seq.size_hint());
let mut values = Vec::<T>::with_capacity(capacity);
while let Some(value) = tri!(seq.next_element()) {
values.push(value);
}
Ok(values)
}
提示来自输入,输入可能是恶意的:一个长度前缀写 2^40、后面只跟 16 字节,照提示 with_capacity 就是一次 8 TiB 的分配。cautious 给提示加了上限:
pub fn cautious<Element>(hint: Option<usize>) -> usize {
const MAX_PREALLOC_BYTES: usize = 1024 * 1024;
if mem::size_of::<Element>() == 0 {
0
} else {
cmp::min(
hint.unwrap_or(0),
MAX_PREALLOC_BYTES / mem::size_of::<Element>(),
)
}
}
即 min(hint.unwrap_or(0), 1 MiB / size_of::<T>()):不管元素多大,预分配最多 1 MiB;没有提示就是 0;零大小类型不需要缓冲,也是 0。超过上限的部分照常靠 push 增长,数据真有那么多时不受影响,只是不再「先信后验」。
用一个记录「最大单次分配」的全局分配器看(src/bin/cautious.rs,分配器部分省略,只在闭包运行期间记录 layout.size() 的最大值):
fn main() {
// bincode 1.x: a Vec is a u64 length prefix followed by the elements.
let mut evil = Vec::new();
evil.extend_from_slice(&(1u64 << 40).to_le_bytes()); // claims 2^40 elements
evil.extend_from_slice(&[0u8; 16]); // but only 16 bytes follow
let (r, max) = biggest(|| bincode::deserialize::<Vec<u64>>(&evil));
println!("Vec<u64> : {:?}, largest alloc = {max} bytes", r.map(|v| v.len()));
let (r, max) = biggest(|| bincode::deserialize::<Vec<u8>>(&evil));
println!("Vec<u8> : {:?}, largest alloc = {max} bytes", r.map(|v| v.len()));
let (r, max) = biggest(|| bincode::deserialize::<Vec<[u64; 32]>>(&evil));
println!("Vec<[u64; 32]>: {:?}, largest alloc = {max} bytes", r.map(|v| v.len()));
// Zero-sized elements need no input bytes, so keep this claim small.
let units = bincode::serialize(&(1u64 << 20)).unwrap();
let (r, max) = biggest(|| bincode::deserialize::<Vec<()>>(&units));
println!("Vec<()> : {:?}, largest alloc = {max} bytes", r.map(|v| v.len()));
// Exact hint vs no hint.
let ten: Vec<u32> = (0..10).collect();
let from_bincode: Vec<u32> = bincode::deserialize(&bincode::serialize(&ten).unwrap()).unwrap();
let from_json: Vec<u32> = serde_json::from_str(&serde_json::to_string(&ten).unwrap()).unwrap();
println!("bincode: len {} cap {}", from_bincode.len(), from_bincode.capacity());
println!("json : len {} cap {}", from_json.len(), from_json.capacity());
}
Vec<u64> : Err(Io(Kind(UnexpectedEof))), largest alloc = 1048576 bytes
Vec<u8> : Err(Io(Kind(UnexpectedEof))), largest alloc = 1048576 bytes
Vec<[u64; 32]>: Err(Io(Kind(UnexpectedEof))), largest alloc = 1048576 bytes
Vec<()> : Ok(1048576), largest alloc = 0 bytes
bincode: len 10 cap 10
json : len 10 cap 16
- 三种元素大小(8、1、256 字节)的最大分配都正好是 1 MiB,即 131072、1048576、4096 个元素。读到第三个元素就遇到输入结束,报
UnexpectedEof。 Vec<()>读了 2^20 个元素,一次分配都没有。- 准确的提示让
Vec一次分配到位(cap 10);serde_json 没有提示,从 0 开始按push增长到 16。
用 cautious 的不止 Vec:VecDeque、BinaryHeap、HashSet 在 seq_impl! 的调用里各传一个用 size_hint::cautious::<T>(seq.size_hint()) 算容量的构造表达式,HashMap 按 (K, V) 的大小算,缓冲主线上的 Content 也用它(见 Content)。BTreeSet、LinkedList 和 BTreeMap 不能预分配,BTreeSet 和 LinkedList 的 reserve 位置填的是一个什么也不做的 nop_reserve。
0.9.8 加上了第一版上限:PR #774(「Clamp hints coming from untrusted input to 4096」)把 ByteBuf、Content 和各集合 Visitor 预分配时用的 size_hint().0 都套上了 cmp::min(.., 4096)。0.9.8 的发布说明写:「This mitigates some types of attacks where untrusted input can instigate the allocation of large vectors or maps.」之后它收拢成 size_hint::cautious,函数体就是 cmp::min(hint.unwrap_or(0), 4096)。
4096 是元素个数,对 u8 太小。postcard 的作者在 issue #2494 报告:一个用户在内存有限的嵌入式目标上反序列化长度 9000 的 Vec<u8>,「were panicking because they failed to allocate 16KiB」,而 postcard 事先就知道长度。原因就是只预分配了 4096 个,剩下的靠 Vec 翻倍增长。PR #2495(「Allow larger preallocated capacity for smaller elements」,首次发布于 1.0.168)把它改成了上面的泛型版本:上限按字节算,1 MiB 除以元素大小。u8 的上限因此从 4096 变成 1048576,9000 字节的 Vec<u8> 一次分配到位;元素大于 256 字节时上限反而比 4096 小。
HashMap 与 BTreeMap
映射的 Serialize 是 serializer.collect_map(self)。反序列化用 map_impl!,visit_map 只有一个循环:
fn visit_map<A>(self, mut $access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut values = $with_capacity;
while let Some((key, value)) = tri!($access.next_entry()) {
values.insert(key, value);
}
Ok(values)
}
$with_capacity 对 BTreeMap 是 BTreeMap::new(),对 HashMap 是 HashMap::with_capacity_and_hasher(size_hint::cautious::<(K, V)>(map.size_hint()), S::default())。哈希器只要求 BuildHasher + Default,所以换了哈希器的 HashMap 也能直接反序列化。
循环里没有任何检查,重复的键由 insert 覆盖:
HashMap <- {"a":1,"a":2} : {"a": 2}
Point <- {"x":1,"x":2} : Err("duplicate field `x` at line 1 column 10")
BTreeMap -> {"a":1,"b":2}
(Point 是一个 #[derive(Deserialize)] struct Point { x: i32 }。)输出时 BTreeMap 按键排序,HashMap 按迭代顺序,每次运行可能不同。serde_json 要求键是字符串,HashMap<u32, _> 能用、HashMap<(u32, u32), _> 报 key must be a string,这些属于格式,见 JSON Serializer。
问: derive 出来的结构体遇到重复字段报 duplicate field,HashMap 遇到重复键为什么一声不吭?
答: 重复检查不是格式做的,是各自的 Visitor 做的。derive 生成的 visit_map 给每个字段留一个 Option 槽,next_key 读到某个字段时槽里已经有值,就返回 duplicate_field(见生成的 Deserialize)。map_impl! 的 Visitor 没有这种状态,只管 insert,HashMap::insert 的语义是覆盖。要拒绝重复键,可以用 serde_with 3.24.0 的 MapPreventDuplicates:
HashMap : {"a": 2}
MapPreventDuplicates: Err("invalid entry: found duplicate key at line 1 column 18")
(第二行是 #[serde_as(as = "MapPreventDuplicates<_, _>")] m: HashMap<String, u8> 读 {"m":{"a":1,"a":2}}。)
Box、Rc、Arc:共享不会被保留
序列化一侧,&T、&mut T、Box<T>、Rc<T>、Arc<T>、Cow<T> 都由 deref_impl! 生成,函数体是 (**self).serialize(serializer):解引用,把里面的值完整地序列化一遍。数据模型里没有「引用」或「对象标识」,同一个 Rc 被引用两次,就写出两份。
反序列化一侧,Box<T> 是 forwarded_impl! 生成的 Deserialize::deserialize(deserializer).map(Box::new),Rc 和 Arc 再在 Box 上转一次(box_forwarded_impl!):
impl<'de, T> Deserialize<'de> for $t<T>
where
T: ?Sized,
Box<T>: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Box::deserialize(deserializer).map(Into::into)
}
}
约束写成 Box<T>: Deserialize 而不是 T: Deserialize,是为了让 T: ?Sized 也成立:Box<str>、Box<[T]> 有自己的 impl,于是 Rc<str>、Arc<[T]> 也能反序列化。每次都是一个新的 Box 转成的新 Rc,强引用计数是 1。
Weak 序列化时先 upgrade(),按 Option 写出,已经失效的 Weak 写成 null;反序列化时把内容按 Option<T> 读完再扔掉,返回 Weak::new(),永远升级不了。
#[derive(Serialize, Deserialize, Debug)]
struct Pair {
a: Rc<String>,
b: Rc<String>,
}
// main 里:
let shared = Rc::new("hello".to_string());
let pair = Pair { a: shared.clone(), b: shared.clone() };
let json = serde_json::to_string(&pair).unwrap();
println!("Pair -> {json}");
let back: Pair = serde_json::from_str(&json).unwrap();
println!(
"before: ptr_eq={} strong={}; after: ptr_eq={} strong(a)={} strong(b)={}",
Rc::ptr_eq(&pair.a, &pair.b),
Rc::strong_count(&pair.a),
Rc::ptr_eq(&back.a, &back.b),
Rc::strong_count(&back.a),
Rc::strong_count(&back.b)
);
let w: Weak<String> = serde_json::from_str("\"hello\"").unwrap();
println!("Weak <- \"hello\" : upgrade = {:?}", w.upgrade());
let dangling = Rc::downgrade(&Rc::new(1u8));
println!("dead Weak -> {}", serde_json::to_string(&dangling).unwrap());
Pair -> {"a":"hello","b":"hello"}
before: ptr_eq=true strong=3; after: ptr_eq=false strong(a)=1 strong(b)=1
Weak <- "hello" : upgrade = None
dead Weak -> null
序列化前 a 和 b 是同一个分配(strong=3:shared、a、b);读回来是两个各自计数为 1 的 Rc。这些 impl 都要开 serde 的 rc feature。不开时,同样的 #[derive(Serialize)] struct Pair { a: Rc<String> } 编译不过(只依赖 serde = { version = "=1.0.229", features = ["derive"] } 的另一个 crate):
error[E0277]: the trait bound `Rc<std::string::String>: serde::Serialize` is not satisfied
--> src/main.rs:5:10
|
5 | #[derive(Serialize)]
| ^^^^^^^^^ the trait `Serialize` is not implemented for `Rc<std::string::String>`
6 | struct Pair {
7 | a: Rc<String>,
| - required by a bound introduced by this call
rc feature 是 1.0.0 前夕加的issue #194(「Rc<> gets deserialized incorrectly」)列出了「creates copies of the references objects」的几个后果:内存变多、按地址比较相等会坏、内部可变性不会反映到各个副本上,并提议「the support should be either fixed or deprecated」。PR #864(「Put rc impls behind a cfg」,首次发布于 1.0.0)没有去「修」,而是把 Rc / Arc 的 impl 挪到 #[cfg(feature = "rc")] 后面,并在 Cargo.toml 里加了今天还在的那段注释:「Serializing and deserializing these types does not preserve identity and may result in multiple copies of the same data. Be sure that this is what you want before enabling this feature.」1.0.0 的发布说明把它列为破坏性改动之一:「A single Rc could be serialized multiple times as part of a data structure, then deserialize to multiple copies of itself. If this is the behavior you want, opt in to these impls by enabling the "rc" feature of your Serde dependency.」
所以 rc 不是「功能还不成熟」的开关,行为一直是上面实验里那样;开关的作用是让依赖它的人明确表示知道这一点。
Duration 与 SystemTime:两个字段的结构体
数据模型里没有时间类型。Duration 的 Serialize 把它当成一个两字段的结构体:
let mut state = tri!(serializer.serialize_struct("Duration", 2));
tri!(state.serialize_field("secs", &self.as_secs()));
tri!(state.serialize_field("nanos", &self.subsec_nanos()));
state.end()
SystemTime 先 duration_since(UNIX_EPOCH),字段叫 secs_since_epoch / nanos_since_epoch,早于 1970 年的时间直接报错。Duration 的 Deserialize 上面有一段注释:
// This is a cleaned-up version of the impl generated by:
//
// #[derive(Deserialize)]
// #[serde(deny_unknown_fields)]
// struct Duration {
// secs: u64,
// nanos: u32,
// }
也就是手写了 derive 会生成的东西:字段标识枚举 Field、visit_seq(按位置读)和 visit_map(按名字读,查重复、查缺失)。Field 上方另有一句注释「If this were outside of the serde crate, it would just use」#[derive(Deserialize)]:在 serde 自己的 crate 里用不了 derive,只能照着展开的样子写。和 derive 不同的是两处 check_overflow:secs 加上 nanos / 1_000_000_000 溢出 u64 时报错,因为 Duration::new 在这种情况下会 panic。
Duration -> {"secs":1,"nanos":500000000}
SystemTime -> {"secs_since_epoch":1700000000,"nanos_since_epoch":5}
SystemTime(before epoch) -> Err("SystemTime must be later than UNIX_EPOCH")
Duration <- [1, 2000000000] : 3s
Duration <- {"secs":1} : Err("missing field `nanos` at line 1 column 10")
Duration <- [u64::MAX, 1e9] : Err("overflow deserializing Duration at line 1 column 34")
[1, 2000000000] 能读:nanos 超过 10^9 不算错,Duration::new 把多出的 2 秒进位,结果是 3 秒。JSON 里存的不是 RFC 3339 时间串,要那种格式得用 chrono / time 这类 crate 自己的 serde 支持或 serialize_with。
PhantomData 与 IgnoredAny:不带数据的两种
PhantomData<T> 序列化成 serialize_unit_struct("PhantomData"),反序列化调 deserialize_unit_struct,PhantomDataVisitor 只收 visit_unit。JSON 里是 null:
PhantomData -> null
PhantomData <- 0 : Err("invalid type: integer `0`, expected unit at line 1 column 1")
IgnoredAny 正相反:什么都收,什么都不留。它既是类型又是自己的 Visitor,每个 visit_* 都返回 Ok(IgnoredAny);visit_seq / visit_map 递归地用 IgnoredAny 把每个元素读完:
#[inline]
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
while let Some((IgnoredAny, IgnoredAny)) = tri!(map.next_entry()) {
// Gobble
}
Ok(IgnoredAny)
}
它的 Deserialize 调 deserialize_ignored_any,这是给格式的提示:「这个值不要了」。serde_json 的 deserialize_ignored_any 收到这个提示走 ignore_value,只扫过输入:字符串不解码、不复制(scratch 在这里只用来记嵌套的括号),也不回调任何 visit_*,扫完调一次 visit_unit。derive 生成的 visit_map 遇到未知字段就用它(struct_.rs):
_ => { let _ = _serde::de::MapAccess::next_value::<_serde::de::IgnoredAny>(&mut __map)?; }
自己写 Visitor 时也可以用它跳过不关心的元素:
IgnoredAny <- nested : IgnoredAny
(IgnoredAny, IgnoredAny, u8) : 7
(第一行读 {"a":[1,2,{"b":null}],"c":"x"},第二行读 ["skip", {"me":1}, 7] 只取第三个元素。)
其他:Result、包装类型与 is_human_readable
几类不需要单独一节、但值得知道的:
| 类型 | 表示 | 备注 |
|---|---|---|
Result<T, E> | 外部标签 enum:serialize_newtype_variant("Result", 0, "Ok", value) | serde_core 的 result feature;serde 依赖 serde_core 时总是打开它 |
NonZero* | 序列化成 self.get() | 反序列化见上文整数一节 |
Wrapping<T>、Saturating<T>、Reverse<T> | 序列化成里面的 T | 反序列化 Saturating 越界取边界值,Wrapping 不回绕 |
Cell<T>、RefCell<T>、Mutex<T>、RwLock<T>、Atomic* | 里面的值 | Mutex / RwLock 被毒化时序列化报错 |
IpAddr、Ipv4Addr、SocketAddr 等 | 看 is_human_readable() | 下面细说 |
Result 的实验输出:
Result::Err -> {"Err":"boom"}
Result <- {"Ok":3} : Ok(3)
网络地址是 is_human_readable 的典型用户(这个方法本身见 Serialize 与 Serializer)。IpAddr 的 Serialize:
if serializer.is_human_readable() {
match *self {
net::IpAddr::V4(ref a) => a.serialize(serializer),
net::IpAddr::V6(ref a) => a.serialize(serializer),
}
} else {
match *self {
net::IpAddr::V4(ref a) => {
serializer.serialize_newtype_variant("IpAddr", 0, "V4", a)
}
net::IpAddr::V6(ref a) => {
serializer.serialize_newtype_variant("IpAddr", 1, "V6", a)
}
}
}
人读的格式写字符串,Ipv4Addr 用一张两位数查找表 DEC_DIGITS_LUT 把四个字节写进栈上的 [u8; 15],不分配;二进制格式写 enum 变体加原始字节。反序列化对称:人读格式走 FromStrVisitor(s.parse(),错误经 Error::custom 转过去),二进制格式读字节数组。
IpAddr json="127.0.0.1" bincode=[0, 0, 0, 0, 127, 0, 0, 1]
SocketAddr json="127.0.0.1:8080" bincode=[0, 0, 0, 0, 127, 0, 0, 1, 144, 31]
IpAddr <- "300.0.0.1" : Err("invalid IP address syntax at line 1 column 11")
bincode 1.3.3 的 is_human_readable() 返回 false:前 4 字节是变体下标 0(u32 小端),接着 4 个八位组;SocketAddr 末尾两字节是端口 8080(0x1F90,小端写成 144、31)。同一个类型在两种格式里结构不同,所以 is_human_readable 必须是格式在序列化和反序列化两侧给出同一个答案。
动脑筋答案
u8 <- 5u128 : Err(Error("invalid type: integer `5` as u128, expected u8"))
u64 <- 5u128 : Err(Error("invalid type: integer `5` as u128, expected u64"))
i128 <- 5u128 : Ok(5)
u128 <- 5u8 : Ok(5)
第一行是错误,而且是 invalid type,不是 invalid value:
u8的那次impl_deserialize_num!调用只列了visit_i8到visit_i64、visit_u8到visit_u64,没有visit_u128。visit_u128于是落到Visitor的默认实现,它把值格式化成integer `5` as u128,报invalid_type(Unexpected::Other(..))。值是多少根本没看。u64一样。只有i128/u128的调用里有num_128!,它按 128 位比较范围,5在范围内,Ok(5)。- 反方向没有问题:
u128的列表里有num_as_self!(u8:visit_u8 ...)。
这说明「小整数能不能读」取决于格式调哪个 visit_*。serde_json 只调 visit_u64 / visit_i64,所以碰不到这个问题;一个把所有整数都当 128 位交给 Visitor 的格式,就会让 u8 字段全部失败。
小结
- 整数的
Visitor全部由impl_deserialize_num!生成,每一对「输入类型 → 目标类型」用num_self!/num_as_self!/int_to_int!/int_to_uint!/uint_to_self!之一:越界报invalid value: integer300, expected u8,浮点报invalid type。NonZero*多查 0,Saturating取边界,Wrapping和内部类型报一样的错。 isize/usize序列化成i64/u64;小整数类型的Visitor不收visit_i128/visit_u128。String收visit_str/visit_string/ 两种字节;&str只收借用的两种。Option调deserialize_option让格式判断,JSON 里Some(None)和None一样是null。- 序列预分配用
size_hint::cautious:min(hint.unwrap_or(0), 1 MiB / size_of::<T>()),零大小类型为 0。0.9.8 起是 4096 个元素(PR #774),1.0.168 改成按字节(PR #2495)。 - 映射的
visit_map只insert,重复键后者覆盖;HashMap按(K, V)大小预分配,BTreeMap不预分配。 Box/Rc/Arc序列化时解引用、反序列化时经Box新建,共享不保留,Rc和Arc要开rcfeature(1.0.0 起,PR #864);Weak读回来永远升级不了。Duration是{secs, nanos}结构体,SystemTime是距 UNIX 纪元的{secs_since_epoch, nanos_since_epoch};PhantomData是单元结构体,IgnoredAny用deserialize_ignored_any让格式跳过整个值。- 网络地址按
is_human_readable()分两种表示:JSON 里是字符串,bincode 里是 enum 下标加原始字节。