生成的 Serialize
主线位置:序列化主线的起点。一次
to_string里,Post::serialize是第一个被调用的用户侧代码,它决定调Serializer的哪个方法、传什么名字和长度、按什么顺序交出字段。本页逐个看serde_derive/src/ser.rs为每种类型形状生成的函数体。trait 本身的约定(状态对象、len的意义)在 Serialize 与 Serializer,属性怎么解析在 属性,生成的代码怎么被包进const _: () = { ... }在 派生宏的流水线。
flowchart TB
body[serialize_body] --> tr{transparent}
tr -->|是| t1["字段自己的 serialize"]
tr -->|否| into{"into = T"}
into -->|是| i1["clone 再 Into 成 T"]
into -->|否| shape{类型形状}
shape -->|具名字段| fl{有 flatten}
fl -->|否| s1[serialize_struct]
fl -->|是| s2["serialize_map(None)"]
shape -->|元组| s3[serialize_tuple_struct]
shape -->|一个字段| s4[serialize_newtype_struct]
shape -->|无字段| s5[serialize_unit_struct]
shape -->|enum| s6["match 每个变体"]
ser.rs 一共 1369 行,没有一行运行在用户程序里:它是过程宏,把 quote! 拼出来的 token 交给编译器。读它的办法是对照 cargo expand 的结果看:每个 quote_block! / quote_expr! 里的 #name 是插值,展开出来就是具体的类型名、字段名和表达式。本页所有展开都来自 serde_derive 1.0.229,私有路径写作 _serde::__private229::...,原样保留;外层的 const _ 包装和属性都删掉了。实验在 M2 Max、macOS、rustc 1.99.0 上跑,依赖 serde = "=1.0.229"(derive)、serde_json = "=1.0.154"。
入口:serialize_body 按形状分派
expand_derive_serialize 解析出 Container 之后,函数体只由 serialize_body 决定:
fn serialize_body(cont: &Container, params: &Parameters) -> Fragment {
if cont.attrs.transparent() {
serialize_transparent(cont, params)
} else if let Some(type_into) = cont.attrs.type_into() {
serialize_into(params, type_into)
} else {
match &cont.data {
Data::Enum(variants) => serialize_enum(params, variants, &cont.attrs),
Data::Struct(Style::Struct, fields) => serialize_struct(params, fields, &cont.attrs),
Data::Struct(Style::Tuple, fields) => {
serialize_tuple_struct(params, fields, &cont.attrs)
}
Data::Struct(Style::Newtype, fields) => {
serialize_newtype_struct(params, &fields[0], &cont.attrs)
}
Data::Struct(Style::Unit, _) => serialize_unit_struct(&cont.attrs),
}
}
}
transparent 和 into 两个容器属性优先:它们把整个类型的序列化交给别的类型,形状就不重要了。其余按 Data 的五种形状各走一个函数。Style::Newtype 是「恰好一个无名字段」,和多字段的元组结构体分开,因为数据模型里它们是两种类型(newtype_struct 和 tuple_struct)。
外层的 impl 也在 expand_derive_serialize 里拼:普通类型是 impl _serde::Serialize for #ident,带 #[serde(remote = "...")] 的类型则是一个固有方法,后面单独讲。
struct:serialize_struct 与字段数
具名字段的 struct 走 serialize_struct。它先断言字段数不超过 u32::MAX,再看有没有没被跳过的 flatten 字段:没有就走 serialize_struct_as_struct,有就走 serialize_struct_as_map。
先看没有 flatten 的情况。src/bin/ser_struct.rs 里的 Post 三个字段,一个带 skip_serializing_if,一个带 skip:
#[derive(Serialize)]
#[allow(dead_code)]
struct Post {
title: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
tags: Vec<String>,
#[serde(skip)]
cache: u64,
}
展开:
impl _serde::Serialize for Post {
fn serialize<__S>(
&self,
__serializer: __S,
) -> _serde::__private229::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
let mut __serde_state = _serde::Serializer::serialize_struct(
__serializer,
"Post",
false as usize + 1 + if Vec::is_empty(&self.tags) { 0 } else { 1 },
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"title",
&self.title,
)?;
if !Vec::is_empty(&self.tags) {
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"tags",
&self.tags,
)?;
} else {
_serde::ser::SerializeStruct::skip_field(&mut __serde_state, "tags")?;
}
_serde::ser::SerializeStruct::end(__serde_state)
}
}
模板是 serialize_struct_as_struct,长度这一段是:
let mut serialized_fields = fields
.iter()
.filter(|&field| !field.attrs.skip_serializing())
.peekable();
let let_mut = mut_if(serialized_fields.peek().is_some() || tag_field_exists);
let len = serialized_fields
.map(|field| match field.attrs.skip_serializing_if() {
None => quote!(1),
Some(path) => {
let field_expr = get_member(params, field, &field.member);
quote!(if #path(#field_expr) { 0 } else { 1 })
}
})
.fold(
quote!(#tag_field_exists as usize),
|sum, expr| quote!(#sum + #expr),
);
逐项对照展开结果:
skip的字段在filter里就没了。cache既不计数,也不生成任何代码。skip是skip_serializing加skip_deserializing的简写,这里只看前一半。- 起点是
#tag_field_exists as usize。 没有#[serde(tag = "...")]的 struct 是false as usize。这个写法的来历在一次往返的冷知识里。 - 普通字段加
1。title贡献+ 1。 skip_serializing_if的字段加一个if表达式。 判断函数在长度里调一次、在后面的if !...里再调一次。len因此是运行期的值,tags为空时是 1,否则是 2。let_mut。 一个字段都不序列化的 struct 生成let __serde_state = ...,不加mut,否则会有unused_mut警告(mut_if上面的注释写了这个例子)。
字段值的表达式来自 get_member:普通类型是 &self.title;#[repr(packed)] 的类型是 &{self.title},先按值复制出来再取引用,因为不能对未对齐的字段取引用;remote 类型另有两种写法,后面讲。
每个字段的语句由 serialize_struct_visitor 生成。带 skip_serializing_if 的字段,结尾那段是:
dst_fields.push(match skip {
None => ser,
Some(skip) => {
if let Some(skip_func) = struct_trait.skip_field(span) {
quote! {
if !#skip {
#ser
} else {
#skip_func(&mut __serde_state, #key_expr)?;
}
}
} else {
quote! {
if !#skip {
#ser
}
}
}
}
});
skip_func 只有 SerializeStruct 和 SerializeStructVariant 有(StructTrait::skip_field 对 SerializeMap 返回 None),所以 struct 跳过一个字段时会告诉格式一声:调 SerializeStruct::skip_field。它的默认实现什么也不做。本机 registry 里 serde_json 1.0.154、bincode 1.3.3、postcard 1.1.3、toml 0.8、serde_yaml 0.9.34 都没有覆盖它。
用一次往返里的 LogSer 包装器(另加了一个打印 skip_field 的方法)跑一遍,tags 为空和不为空各一次:
-- tags empty
serialize_struct("Post", 1)
serialize_field("title")
serialize_str("a")
skip_field("tags")
end
=> {"title":"a"}
-- tags non-empty
serialize_struct("Post", 2)
serialize_field("title")
serialize_str("a")
serialize_field("tags")
serialize_seq(Some(1))
serialize_element
serialize_str("x")
end
end
=> {"title":"a","tags":["x"]}
两次 len 都等于实际交出的字段数。cache 两次都没出现。
skip_field 是为 CBOR 的「整数键」表示加的#960 提出给 SerializeStruct::serialize_field 传字段序号,理由是 CBOR 有一种用整数代替字段名的紧凑表示,而「conditionally skipped」的字段会让序号对不上(原文:「This would enable CBOR to deserialize correctly from a packed representation (integer keys) with fields that are conditionally skipped」)。#1022(「Inform serializers about skipped fields.」,首次发布在 1.0.13)没有改 serialize_field 的签名,而是在 SerializeStruct 和 SerializeStructVariant 上加了一个有默认实现的 skip_field,derive 在 skip_serializing_if 为真的分支里调它:格式数着 serialize_field 和 skip_field 两种调用,就知道当前是第几个字段。默认实现为空,所以加这个方法不破坏已有的格式。1.0.13 的发布说明写的是「Add an optional skip_field method so serializers can be notified when a field is skipped (#1022)」。这就是上面展开里 else 分支的来历;skip(编译期跳过)的字段不会触发它,元组结构体也没有对应的方法。
带标签的 struct
#[serde(tag = "kind")] 也能用在 struct 上,serialize_struct_tag_field 在所有字段前插一个 serialize_field(tag, 类型名),tag_field_exists 变成 true:
let mut __serde_state = _serde::Serializer::serialize_struct(
__serializer,
"Ping",
true as usize + 1,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"kind",
"Ping",
)?;
输出是 {"kind":"Ping","seq":9},日志里是 serialize_struct("Ping", 2)。
有 flatten:serialize_map(None)
Page 有一个 #[serde(flatten)] 字段:
#[derive(Serialize)]
struct Meta {
views: u32,
}
#[derive(Serialize)]
struct Page {
title: String,
#[serde(flatten)]
meta: Meta,
}
serialize_struct_as_map 不算长度,直接传 None;普通字段改用 serialize_entry,flatten 字段改成把 __serde_state 包进 FlatMapSerializer 再交给字段自己的 serialize:
let mut __serde_state = _serde::Serializer::serialize_map(
__serializer,
_serde::__private229::None,
)?;
_serde::ser::SerializeMap::serialize_entry(
&mut __serde_state,
"title",
&self.title,
)?;
_serde::Serialize::serialize(
&&self.meta,
_serde::__private229::ser::FlatMapSerializer(&mut __serde_state),
)?;
_serde::ser::SerializeMap::end(__serde_state)
&&self.meta 的两层引用是因为 field_expr 已经是 &self.meta,flatten 分支的模板又写了 &#field_expr。日志:
-- flatten
serialize_map(None)
serialize_key
serialize_str("title")
serialize_value
serialize_str("a")
serialize_key
serialize_str("views")
serialize_value
serialize_u32(3)
end
=> {"title":"a","views":3}
Meta::serialize 收到的是 FlatMapSerializer,它照常调 serialize_struct("Meta", 1) 和 serialize_field("views", ..),但 FlatMapSerializer 把这些转成外层 map 上的 serialize_entry。所以日志里 views 和 title 是同一层的两个键,中间没有 serialize_struct。外层不知道 flatten 字段会贡献几个键,只能传 None;格式怎么处理 None(bincode、postcard 报错)在 Serialize 与 Serializer,FlatMapSerializer 的其余细节在 flatten。
元组、newtype 与 unit struct
src/bin/ser_shapes.rs 定义了三种:
#[derive(Serialize)]
struct Meters(f64);
#[derive(Serialize)]
struct Point(i32, #[serde(skip_serializing_if = "is_zero")] i32);
#[derive(Serialize)]
struct Marker;
展开出来分别是一行、一个块、一行:
_serde::Serializer::serialize_newtype_struct(__serializer, "Meters", &self.0)
let mut __serde_state = _serde::Serializer::serialize_tuple_struct(
__serializer,
"Point",
0 + 1 + if is_zero(&self.1) { 0 } else { 1 },
)?;
_serde::ser::SerializeTupleStruct::serialize_field(
&mut __serde_state,
&self.0,
)?;
if !is_zero(&self.1) {
_serde::ser::SerializeTupleStruct::serialize_field(
&mut __serde_state,
&self.1,
)?;
}
_serde::ser::SerializeTupleStruct::end(__serde_state)
_serde::Serializer::serialize_unit_struct(__serializer, "Marker")
serialize_newtype_struct把唯一的字段原样交出去,格式自己决定要不要体现这一层。serde_json 不体现,Meters(1.5)写成1.5。serialize_tuple_struct的长度从0而不是false as usize起算:元组结构体不能带标签。skip_serializing_if同样生效,但跳过时没有skip_field可调,SerializeTupleStruct没有这个方法。serialize_unit_struct只交出名字,serde_json 写成null。
日志:
-- newtype
serialize_newtype_struct("Meters")
serialize_f64(1.5)
=> 1.5
-- tuple
serialize_tuple_struct("Point", 1)
serialize_field
serialize_i32(1)
end
=> [1]
-- unit
serialize_unit_struct("Marker")
=> null
Point(1, 0) 写成了 [1]。元组按位置读,少了一个元素,读回来会怎样,见本页末尾的动脑筋。
所有 serialize_* 的第一个参数都是类型名(#type_name,取 rename 之后的序列化名)。serde_json 不用它,但它是数据模型的一部分,格式可以用来做别的事,比如 serde_json 自己的 serialize_struct 会认两个魔法名字(一次往返)。
enum:外部标签
serialize_enum 生成一个 match *self,每个变体一个分支;serialize_variant 先用 ref 绑定变体的字段,再按容器的标签方式选模板。默认的外部标签用 serialize_externally_tagged_variant。
#[derive(Serialize)]
#[allow(dead_code)]
enum Shape {
Empty,
Circle(f64),
Line(i32, i32),
Rect { w: u32, h: u32 },
#[serde(skip_serializing)]
Secret(String),
}
展开:
match *self {
Shape::Empty => {
_serde::Serializer::serialize_unit_variant(
__serializer,
"Shape",
0u32,
"Empty",
)
}
Shape::Circle(ref __field0) => {
_serde::Serializer::serialize_newtype_variant(
__serializer,
"Shape",
1u32,
"Circle",
__field0,
)
}
Shape::Line(ref __field0, ref __field1) => {
let mut __serde_state = _serde::Serializer::serialize_tuple_variant(
__serializer,
"Shape",
2u32,
"Line",
0 + 1 + 1,
)?;
_serde::ser::SerializeTupleVariant::serialize_field(
&mut __serde_state,
__field0,
)?;
// ... __field1 同样
_serde::ser::SerializeTupleVariant::end(__serde_state)
}
Shape::Rect { ref w, ref h } => {
let mut __serde_state = _serde::Serializer::serialize_struct_variant(
__serializer,
"Shape",
3u32,
"Rect",
0 + 1 + 1,
)?;
_serde::ser::SerializeStructVariant::serialize_field(
&mut __serde_state,
"w",
w,
)?;
// ... h 同样
_serde::ser::SerializeStructVariant::end(__serde_state)
}
Shape::Secret(..) => {
_serde::__private229::Err(
_serde::ser::Error::custom(
"the enum variant Shape::Secret cannot be serialized",
),
)
}
}
- 四种变体形状对应数据模型的四个
*_variant方法,都交出类型名、变体序号、变体名三样。序号是变体在源码里的位置(variant_index as u32),skip_serializing的变体照样占一个号:Secret是 4,它前面的号不会因为它被跳过而改变。 - 字段是
ref绑定的,已经是引用,所以__field0、w直接传,不再加&。 skip_serializing的变体不是编译错误,而是一个运行期一定失败的分支,错误消息在编译期拼好。
日志:
-- unit variant
serialize_unit_variant("Shape", 0, "Empty")
=> "Empty"
-- newtype variant
serialize_newtype_variant("Shape", 1, "Circle")
serialize_f64(0.5)
=> {"Circle":0.5}
-- tuple variant
serialize_tuple_variant("Shape", 2, "Line", 2)
serialize_field
serialize_i32(1)
serialize_field
serialize_i32(2)
end
=> {"Line":[1,2]}
-- struct variant
serialize_struct_variant("Shape", 3, "Rect", 2)
serialize_field("w")
serialize_u32(3)
serialize_field("h")
serialize_u32(4)
end
=> {"Rect":{"w":3,"h":4}}
-- skipped variant
=> Err(the enum variant Shape::Secret cannot be serialized)
外部标签的另一半(反序列化、serde_json 的 {"Variant": ...} 写法)在 外部标签。
另外三种表示调什么
serialize_variant 末尾的 match (cattrs.tag(), variant.attrs.untagged()) 把另外三种表示分给三个函数。它们都不调 *_variant 方法:数据模型里没有「内部标签」这种类型,只能用 struct、map 和 unit 拼出来。src/bin/ser_tags.rs 用同一个 LogSer 跑了每种表示的几种变体,结果整理成表:
| 表示 | 变体 | 日志里的调用 | 输出 |
|---|---|---|---|
tag = "t" | unit | serialize_struct("Internal", 1),serialize_field("t") | {"t":"Unit"} |
tag = "t" | newtype(包 Inner { x }) | serialize_struct("Inner", 2),serialize_field("t"),serialize_field("x") | {"t":"New","x":1} |
tag = "t" | struct | serialize_struct("Internal", 2),先 t 后字段 | {"t":"Rec","a":2} |
tag = "t", content = "c" | unit | serialize_struct("Adjacent", 1),t 的值是 serialize_unit_variant | {"t":"Unit"} |
tag = "t", content = "c" | newtype | serialize_struct("Adjacent", 2),t 同上,c 是内容 | {"t":"New","c":3} |
untagged | unit | serialize_unit | null |
untagged | newtype | 内容自己的 serialize(serialize_u8) | 4 |
untagged | tuple | serialize_tuple(2) | [5,6] |
untagged | struct | serialize_struct("Untagged", 1) | {"a":7} |
几处值得对着源码看的:
- 内部标签的 newtype 变体调的是私有函数
serialize_tagged_newtype,它把 serializer 包进TaggedSerializer。内层Inner自己调serialize_struct("Inner", 1),TaggedSerializer改成serialize_struct("Inner", 2)再先写标签,所以日志里的名字是Inner而不是Internal。 - 相邻标签的标签值是
AdjacentlyTaggedEnumVariant,它的Serialize就是serialize_unit_variant。这样格式拿到的仍是序号加名字,非自描述格式可以只写序号。内容部分(tuple、struct 变体)由一个局部的__AdjacentlyTagged包装类型交出去,见serialize_adjacently_tagged_variant。 - untagged 的 tuple 变体调
serialize_tuple,struct 变体调serialize_struct,名字用的是类型名(serialize_untagged_variant里的cattrs.name())。
序列化一侧这三种表示都不需要缓冲,代价全在反序列化一侧:内部标签与相邻标签、untagged。
serialize_with:__SerializeWith 包装类型
src/bin/ser_with.rs 里给一个 u64 字段换一个序列化函数,写成字符串:
// serialize_with: write a u64 as a string.
fn as_string<S: Serializer>(v: &u64, s: S) -> Result<S::Ok, S::Error> {
s.collect_str(v)
}
#[derive(Serialize)]
struct Order {
#[serde(serialize_with = "as_string")]
id: u64,
}
输出 {"id":"42"}。展开后 serialize_field 的第三个参数变成了一个块表达式:
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"id",
&{
#[doc(hidden)]
struct __SerializeWith<'__a> {
values: (&'__a u64,),
phantom: _serde::__private229::PhantomData<Order>,
}
impl<'__a> _serde::Serialize for __SerializeWith<'__a> {
fn serialize<__S>(
&self,
__s: __S,
) -> _serde::__private229::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
as_string(self.values.0, __s)
}
}
__SerializeWith {
values: (&self.id,),
phantom: _serde::__private229::PhantomData::<Order>,
}
},
)?;
模板在 wrap_serialize_with。块里定义一个局部结构体,持有字段的引用(values 是元组,因为用在整个变体上时要装多个字段),给它实现 Serialize,函数体就是调用户的函数;块的值是这个结构体的一个实例,外面再取引用交给 serialize_field。phantom 带上外层类型,是为了让外层的泛型参数在包装类型里也算「用到了」。'__a 只在有字段时加(field_exprs.is_empty() 时不加),对应 wrap_serialize_variant_with 处理 unit 变体的情况。
用户函数的路径带着它在属性里的 span(quote_spanned! {serialize_with.span()=> ...}),函数签名写错时,rustc 把错误标在 #[serde(serialize_with = "...")] 那个字符串上,源码里的注释画了这个箭头。
问: 生成代码明明能直接写 as_string(&self.id, ...),为什么要绕一个局部类型?
答: 因为这里拿不到该传的那个 serializer。as_string 需要的是「写这个字段的值」的 serializer,它只存在于格式的 SerializeStruct::serialize_field 内部:serde_json 在那里先写键和 :,再用自己的 &mut Serializer 调 value.serialize(..)。serialize_field 的签名只收 &T where T: Serialize,不收函数。要把一个函数送进去,只能做一个实现了 Serialize 的值,让格式在合适的时候回调它,__SerializeWith 就是这个值。同样的原因,flatten 字段要包 FlatMapSerializer、相邻标签的内容要包 __AdjacentlyTagged:数据只能以「实现了 Serialize 的值」的形式交给格式。
#[serde(with = "module")] 在属性解析时就被拆成 serialize_with = "module::serialize" 和 deserialize_with = "module::deserialize" 两个路径(attr.rs 里 meta.path == WITH 那一支往路径末尾 push 一个 serialize / deserialize),之后走的是同一个 wrap_serialize_with。
remote 与 getter
remote 给一个别处定义、自己不能加 derive 的类型写序列化代码。例子里的 other::Span 有一个私有字段 start,只能经 start() 方法读:
mod other {
pub struct Span {
start: u32,
pub len: u32,
}
// ... new、start() 方法
}
#[derive(Serialize)]
#[serde(remote = "other::Span")]
struct SpanDef {
#[serde(getter = "other::Span::start")]
start: u32,
len: u32,
}
#[derive(Serialize)]
struct Token {
#[serde(with = "SpanDef")]
span: other::Span,
}
输出 {"span":{"start":3,"len":5}}。SpanDef 的展开不是 impl Serialize,而是一个固有方法,参数是远程类型的引用 __self:
impl SpanDef {
fn serialize<__S>(
__self: &other::Span,
__serializer: __S,
) -> _serde::__private229::Result<__S::Ok, __S::Error>
where
__S: _serde::Serializer,
{
match _serde::__private229::None::<&SpanDef> {
_serde::__private229::Some(SpanDef { start: __v0, len: __v1 }) => {}
_ => {}
}
let mut __serde_state = _serde::Serializer::serialize_struct(
__serializer,
"SpanDef",
false as usize + 1 + 1,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"start",
_serde::__private229::ser::constrain::<u32>(&other::Span::start(__self)),
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"len",
_serde::__private229::ser::constrain::<u32>(&__self.len),
)?;
_serde::ser::SerializeStruct::end(__serde_state)
}
}
- 为什么不是
impl Serialize for other::Span:孤儿规则不允许给外部类型实现外部 trait。固有方法SpanDef::serialize的签名正好符合serialize_with要的形状,Token里with = "SpanDef"展开成SpanDef::serialize(self.values.0, __s),经的还是上一节的__SerializeWith。 self_var换成了__self(Parameters::new里按is_remote选)。get_member对 remote 类型的每个字段都包一层constrain::<T>,这个函数就是fn constrain<T: ?Sized>(t: &T) -> &T { t },作用是让编译器检查远程类型的字段类型和SpanDef里声明的一致;写错了在这里报类型不匹配。- 带
getter的字段读的是&other::Span::start(__self),没有 getter 的读&__self.len,所以私有字段必须给 getter,公开字段不用。 - 开头那个永远不会进入的
match是pretend_used生成的,让SpanDef的字段不报「从未读取」,见 派生宏的流水线。 - 类型名是
"SpanDef",不是"Span":名字来自 derive 所在的类型。在意名字的格式要另加#[serde(rename = "Span")]。
反序列化一侧有 getter 时,生成代码先构造本地的 SpanDef,再 Into 成远程类型,见 生成的 Deserialize。
into 与 transparent
#[serde(into = "T")] 把整个值换成另一个类型再序列化:
#[derive(Clone, Serialize)]
#[serde(into = "String")]
struct Version {
major: u32,
minor: u32,
}
impl From<Version> for String {
fn from(v: Version) -> String {
format!("{}.{}", v.major, v.minor)
}
}
serialize_into 生成:
_serde::Serialize::serialize(
&_serde::__private229::Into::<
String,
>::into(_serde::__private229::Clone::clone(self)),
__serializer,
)
serialize 只拿到 &self,而 Into::into 要按值消费,所以先 Clone::clone(self):into 要求类型实现 Clone,每次序列化都复制一份。输出是 "1.2"。
#[serde(transparent)] 让一个 struct 序列化得和它唯一的数据字段一模一样:
#[derive(Serialize)]
#[serde(transparent)]
struct Id<T> {
raw: u64,
marker: PhantomData<T>,
}
serialize_transparent 找到被标记为 transparent 的那个字段(属性解析阶段已经挑好),生成一行:
impl<T> _serde::Serialize for Id<T> {
// ...
{
_serde::Serialize::serialize(&self.raw, __serializer)
}
}
和 newtype struct 不同,这里不调 serialize_newtype_struct,格式完全看不到 Id 这一层。Id<String> 写成 7。impl<T> 后面没有 T: Serialize:marker 不参与序列化,bound 规则不给它加约束,见 泛型与 bound。
Point(1, 0) 写出去再读回来,会得到什么?平台 M2 Max、macOS、rustc 1.99.0。src/bin/ser_quiz.rs:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Point(i32, #[serde(skip_serializing_if = "is_zero")] i32);
#[derive(Serialize, Deserialize, Debug)]
struct Point2(i32, #[serde(skip_serializing_if = "is_zero", default)] i32);
#[derive(Serialize, Deserialize, Debug)]
struct Named {
x: i32,
#[serde(skip_serializing_if = "is_zero", default)]
y: i32,
}
fn is_zero(v: &i32) -> bool {
*v == 0
}
fn main() {
let s = serde_json::to_string(&Point(1, 0)).unwrap();
println!("{s} -> {:?}", serde_json::from_str::<Point>(&s).map_err(|e| e.to_string()));
let s = serde_json::to_string(&Point2(1, 0)).unwrap();
println!("{s} -> {:?}", serde_json::from_str::<Point2>(&s).map_err(|e| e.to_string()));
let s = serde_json::to_string(&Named { x: 1, y: 0 }).unwrap();
println!("{s} -> {:?}", serde_json::from_str::<Named>(&s).map_err(|e| e.to_string()));
}
三行输出里,哪几行是 Ok?
动脑筋答案
[1] -> Err("invalid length 1, expected tuple struct Point with 2 elements at line 1 column 3")
[1] -> Ok(Point2(1, 0))
{"x":1} -> Ok(Named { x: 1, y: 0 })
后两行是 Ok。
Point(1, 0)写成[1]。反序列化一侧生成的visit_seq按位置取元素,第二个next_element返回None,字段没有default,于是报invalid_length(1, ..)。expected后面那句「tuple struct Point with 2 elements」是生成代码里写死的字符串。Point2给第二个字段加了default,None时用Default::default(),读回Point2(1, 0)。这只在被跳过的是末尾的元素时成立:中间的元素被跳过,后面的元素会整体前移一位,读进错误的字段。Named是具名字段,JSON 里按名字找,少一个键就走缺省值。这里同样要写default:i32不是Option,没有它就报missing field。
skip_serializing_if 只管写,不管读;读的一侧要靠 default 或 Option 配合。具名字段在 bincode 这类按位置读的格式里也会出同样的问题,见 常见坑。反序列化一侧的 visit_seq 和缺省值在 生成的 Deserialize。
小结
serialize_body先看transparent、into,再按五种形状分派:struct →serialize_struct(有 flatten 时serialize_map(None)),元组 →serialize_tuple_struct,单字段 →serialize_newtype_struct,无字段 →serialize_unit_struct,enum → 一个match。- struct 的字段数是
tag_field_exists as usize加每个字段的1;skip的字段不计,skip_serializing_if的字段变成if 条件 { 0 } else { 1 },所以len是运行期的值,并且跳过时调一次默认为空的skip_field(1.0.13,#1022)。 - 外部标签 enum 的四种变体对应四个
*_variant方法,交出类型名、序号、变体名;skip_serializing的变体变成一个运行期报错的分支,序号不重排。内部标签、相邻标签、untagged 用 struct、unit_variant、tuple、unit 拼出来,不调*_variant。 serialize_with包成一个局部的__SerializeWith类型,因为格式只接受「实现了Serialize的值」;remote生成固有方法SpanDef::serialize(__self: &Remote, ..),字段经constrain::<T>检查类型,私有字段经getter读。into每次序列化先clone再转换;transparent直接调唯一数据字段的serialize,格式看不到外层。