怎么读这一卷
主线位置:三条主线的总览。本页把序列化主线、反序列化主线和缓冲主线各画一遍,标出每一步落在哪个函数上、哪一页讲它;下一部分的「一次往返」用一个完整的例子把前两条线走一遍,并用打印调用的手写
Serializer和Visitor证明顺序。
flowchart TB
start[从这里开始] --> q{读这一卷为了什么}
q -->|会用| use1[01 全景]
use1 --> use2[03 的属性]
use2 --> use3["04 enum 的表示"]
use3 --> use4[07 常见坑]
q -->|读懂实现| all[00 到 09 按顺序]
锁定版本
| 项 | serde | serde_json |
|---|---|---|
| 版本 | 1.0.229(serde、serde_core、serde_derive 同版本) | 1.0.154 |
| tag | v1.0.229 | v1.0.154 |
| commit | 7fc3b4c(2026-07-18,「Release 1.0.229」) | 20572a6(2026-10-11,「Release 1.0.154」) |
rust-version | serde、serde_core 1.56;serde_derive 1.71 | 1.71 |
本站 Cargo.lock | 1.0.228 | 1.0.150 |
源码链接都用 tag,形如 https://github.com/serde-rs/serde/blob/v1.0.229/serde_core/src/ser/mod.rs#L234。trait 的定义在 serde_core/src/ 下,derive 生成的代码要用的运行时(ContentDeserializer、FlatMapDeserializer、missing_field)在 serde/src/private/ 下,两者的分工见源码地图。想在本地对照着读:
git clone https://github.com/serde-rs/serde
git -C serde checkout v1.0.229
git clone https://github.com/serde-rs/json serde_json
git -C serde_json checkout v1.0.154
serde 的 rust-version 是 1.56,serde_derive 是 1.71,两者不一样。serde 的 build.rs 在编译器低于 1.71 时设 no_serde_derive,把 derive 才用得到的 private::de、private::ser 整个关掉:老编译器上仍然能用 trait 和手写的 impl,只是用不了 derive。
1.0.228 到 1.0.229:生成代码只变了一处写法
git log v1.0.228..v1.0.229 有 39 个提交,其中 20 个没有碰这三个 crate 的代码(CI、ui 测试快照、合并提交等)。GitHub 上 1.0.229 的发布说明只有一行:
- Update to syn 3
逐个读改了 serde、serde_core、serde_derive 的提交(git diff --stat 一共 19 个文件,+301 / −282),按「改了谁」归类:
| 改动 | 提交 / PR | 改了什么 | 生成的代码或行为 |
|---|---|---|---|
| syn 2 → 3 | #3085 | 工作区的 syn = "3";bound.rs、attr.rs、receiver.rs 跟着改 API | 不变 |
生成代码少用 ? | #2936(9f9a1ea3) | 外部标签 enum 的 visit_enum、相邻标签 enum 的 visit_seq、deserialize_in_place 的 visit_seq 里,match x? { Some.. None.. } 改成 match x { Ok(Some..) Ok(None..) Err(e) => return Err(e) } | 写法变了,行为不变 |
serde_derive 的最低编译器 | d656b4dd、5a44519a、681270e4 | 1.61 → 1.65 → 1.68 → 1.71;serde 和 serde_core 的 build.rs 里 no_serde_derive 的门槛跟着改成 71 | 不变 |
减少 serde_derive 自己的 llvm-lines | #3034、#3035、#3037、#3038 | 重写 bound.rs 的 with_bound、ast.rs 的 fields_from_ast、ser.rs 的 serialize_struct_visitor 等处的迭代器链 | 不变 |
| 其余重构 | fae23fea、086353c5、7d8427c0 等 | let else、少一层嵌套、注释拼写 | 不变 |
serde_core 的 seq_impl! | cfe66924 | visit_seq(mut self, ...) 里的 &mut self.0 改成 &mut *self.0,消掉 clippy 的 mut_mut | 不变 |
serde 的 unstable | fa7da4a9 | #![feature(never_type)] 只在 docsrs 下开(serde 自己的代码不用 !) | 不变 |
「减少 llvm-lines」说的是 serde_derive 这个过程宏 crate 自身编译出来的 LLVM IR 行数,不是它生成给用户的代码。#3034 的说明是「2223 less llvm-lines in serde_derive. Might compile faster. Not tested.」,给出的数字是 before: 143868、after: 141645。这几个提交让 serde_derive 编译得快一点,展开出来的东西不变。
光读 diff 只能说「应该不变」。把同一个文件分别交给两个版本的 derive 展开,再比一比。文件里有一个带 skip_serializing_if 的 struct、一个外部标签 enum、一个相邻标签 enum:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Post {
title: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
tags: Vec<String>,
}
#[derive(Serialize, Deserialize)]
enum Shape {
Circle(f64),
Rect { w: f64, h: f64 },
}
#[derive(Serialize, Deserialize)]
#[serde(tag = "t", content = "c")]
enum Event {
Ping,
Move(i32, i32),
}
fn main() {
let post = Post { title: "a".into(), tags: vec![] };
println!("{}", serde_json::to_string(&post).unwrap());
println!("{}", serde_json::to_string(&Shape::Rect { w: 1.0, h: 2.0 }).unwrap());
println!("{}", serde_json::to_string(&Shape::Circle(0.5)).unwrap());
println!("{}", serde_json::to_string(&Event::Move(1, 2)).unwrap());
println!("{}", serde_json::to_string(&Event::Ping).unwrap());
}
两个 crate 的依赖分别是 serde = { version = "=1.0.229", features = ["derive"] }、serde_json = "=1.0.154",和 serde = { version = "=1.0.228", features = ["derive"] }、serde_json = "=1.0.150"。M2 Max、macOS、rustc 1.99.0 上两边的输出一样:
{"title":"a"}
{"Rect":{"w":1.0,"h":2.0}}
{"Circle":0.5}
{"t":"Move","c":[1,2]}
{"t":"Ping"}
cargo expand 的结果:1.0.229 一侧 1 429 行,1.0.228 一侧 1 418 行。把 1.0.228 一侧的 __private228 全部替换成 __private229 再 diff,剩下 46 行、8 处差异,都在两个 enum 的 Deserialize 里:Shape 的 visit_enum 3 处,Event 的 visit_seq 5 处。三个 Serialize impl 和 Post 的 Deserialize 一字不差。第一处是这样的:
467,468c467,468
< match _serde::de::EnumAccess::variant(__data)? {
< (__Field::__field0, __variant) => {
---
> match _serde::de::EnumAccess::variant(__data) {
> _serde::__private229::Ok((__Field::__field0, __variant)) => {
对应 serde_derive 里 enum_externally.rs 第 72 行的 match _serde::de::EnumAccess::variant(__data) {,match 的最后一个分支是新加的 Err(__err) => Err(__err)。#2936 的提交说明是「This may slightly improve compilation time and costs nothing」:? 隐含一次 From::from 错误转换,而这里进出的是同一个错误类型,用不着。serde 和 serde_core 自己的代码早就是这么写的,lib.rs 里用 #![deny(clippy::question_mark_used)] 禁止 ?,改用一个 tri! 宏,注释说它「gives a 5.5% improvement in compile time compared to standard try!, and 9% improvement compared to ?」。
另一个变化是私有模块的名字:生成代码里所有 _serde::__private228::... 都变成了 _serde::__private229::...。这个名字每个补丁版本都变,原因见源码地图。结论是:1.0.228 到 1.0.229 对使用者来说只是换了一种生成写法,序列化的输出和反序列化的行为都不变;本站受不受影响,放在升级一章逐条核对。
serde_json 1.0.150 到 1.0.154
git log v1.0.150..v1.0.154 有 26 个提交,改了 src/ 的有下面这些。PR 号和版本取自各版本的发布说明,「生效条件」一列是读 diff 得到的:
| 版本 | PR | 改了什么 | 生效条件 |
|---|---|---|---|
| 1.0.151 | #1331 | 新增 RawValue::from_string_unchecked,debug 构建下断言输入是良构、无空白的 JSON | 开 raw_value 且调用它 |
| 1.0.152 | #1346 | arbitrary_precision 下 -0 不再被解析成 i64 的 0,而是原样留成字符串 "-0",负号得以保留 | 开 arbitrary_precision |
| 1.0.152 | #1347 | Number::as_f32 遇到超出 f32 范围的浮点数返回 None,于是 Value == f32 对这类数为 false(发布说明:「Fix comparison of large non-integer values with f32::INFINITY」) | 比较 Value 和 f32 |
| 1.0.153 | #1345 | 非法 UTF-8 的报错位置指向第一个坏字节,多行输入的行列号也改对了(read.rs) | 输入里有非法 UTF-8 |
| 1.0.154 | #1333 | preserve_order 不再隐含 std,改成 ["indexmap", "alloc", "dep:foldhash"],no_std 下也能用 | 开 preserve_order |
另有 577729e 把 lexical/math.rs 里的 usize::max_value() 换成 usize::MAX,Cargo.toml 删掉了 authors,都不改行为。前面那段程序里 serde_json 两个版本的输出一样,也和这张表相符:五项改动都不在默认 feature 下的序列化、反序列化路径上。
三条主线
serde 的核心是两对 trait:类型一侧的 Serialize / Deserialize,格式一侧的 Serializer / Deserializer,中间是 29 种类型的数据模型。全卷沿三条线展开,每页开头都会标出本页在哪条主线上。下面三节用的例子都是:
#[derive(Serialize, Deserialize)]
struct Post {
title: String,
tags: Vec<String>,
}
序列化主线:一次 to_string
flowchart TB callser["serde_json::to_string"] --> newser["Serializer::new"] newser --> impl["Post::serialize"] impl --> sstruct["serialize_struct"] sstruct --> obj["写左花括号"] obj --> field["serialize_field 每个字段"] field --> key["写键和冒号"] key --> val["值递归 serialize"] val --> field field --> endnode["end 写右花括号"]
| 步骤 | 源码 | 讲它的页 |
|---|---|---|
1. derive 为 Post 生成 impl Serialize:serialize_struct("Post", 字段数),每个字段一次 serialize_field,最后 end | expand_derive_serialize、serialize_struct_as_struct | 生成的 Serialize |
2. 这些调用都是 trait 方法:Serialize 只有一个 serialize<S: Serializer>;Serializer 的 serialize_struct 返回一个实现 SerializeStruct 的状态对象 | serde_core/src/ser/mod.rs | Serialize 与 Serializer |
3. to_string → to_vec(先开 128 字节的 Vec)→ to_writer:Serializer::new(writer),然后 value.serialize(&mut ser) | serde_json ser.rs | serde_json 序列化一侧 |
4. serde_json 的 serialize_struct 直接转给 serialize_map:begin_object 写 {,返回 Compound 的 Map 变体,状态是 State::First | 同上 | 同上 |
5. serialize_field 转给 serialize_entry,即 serialize_key(begin_object_key(.., state == First) 决定写不写逗号,然后把状态改成 Rest)加 serialize_value(写 :,值再递归调 Serialize::serialize) | 同上 | 同上 |
6. end 转给 SerializeMap::end,end_object 写 } | 同上 | 同上 |
7. 逗号、冒号、缩进由 Formatter 的钩子决定;默认 CompactFormatter,to_string_pretty 用 PrettyFormatter | 同上 | 同上 |
整条线上只有类型在「推」:Post::serialize 决定先写哪个字段、写几个,格式只是被一个个方法调用。serde_json 不知道 Post 是什么,Post 也不知道输出是 JSON。
反序列化主线:一次 from_str
flowchart TB
callde["serde_json::from_str"] --> impl["Post::deserialize"]
impl --> dstruct["deserialize_struct"]
dstruct --> peek{首字节}
peek -->|左花括号| vmap["__Visitor::visit_map"]
peek -->|左方括号| vseq["__Visitor::visit_seq"]
vmap --> nkey["next_key 得 __Field"]
nkey --> nval["next_value 取字段值"]
nval --> nkey
nkey -->|没有键了| fill["缺的字段 missing_field"]
fill --> fin["end 检查尾部"]
| 步骤 | 源码 | 讲它的页 |
|---|---|---|
1. derive 生成 impl Deserialize:一个字段标识枚举 __Field(加 __ignore)、它的 __FieldVisitor、一个 __Visitor;deserialize 只做一件事,调 deserialize_struct("Post", FIELDS, __Visitor) | expand_derive_deserialize、de/struct_.rs、de/identifier.rs | 生成的 Deserialize |
2. Deserialize<'de> 把一个 Deserializer 交给类型;Deserializer 的 deserialize_struct 接一个 Visitor,格式读到什么就调它的哪个 visit_* | serde_core/src/de/mod.rs | Deserialize、Deserializer 与 Visitor |
3. from_str 用 StrRead 包住输入,交给 from_trait:Deserializer::new、Deserialize::deserialize(&mut de),最后 end 检查后面只剩空白,否则报 TrailingCharacters | serde_json de.rs | serde_json 反序列化一侧 |
4. serde_json 的 deserialize_struct 看首字节:{ 调 visitor.visit_map(MapAccess::new(self)),[ 调 visit_seq,别的报类型错误 | 同上 | 同上 |
5. __Visitor::visit_map 循环 next_key:serde_json 的 next_key_seed 把键交给 MapKey,后者的 deserialize_any 读出字符串,回调 visit_borrowed_str 或 visit_str,__FieldVisitor 把 "title" 变成 __Field::__field0 | 同上、MapAccess | SeqAccess、MapAccess 与 EnumAccess |
6. 按字段 next_value::<T>()(serde_json 的 next_value_seed 先吃掉 :);同一字段第二次出现报 duplicate_field,没认出的键读成 IgnoredAny 扔掉 | 同上 | 生成的 Deserialize |
7. 循环结束后,还是 None 的字段交给 missing_field:类型是 Option<T> 时得到 None,否则报 missing field | serde/src/private/de.rs | 同上 |
8. Deserialize<'de> 的 'de 是输入的生命周期:visit_borrowed_str 让 &'de str 字段直接指向输入,有转义时只能 visit_str | serde_json read.rs | 生命周期 'de 与借用 |
和序列化主线比,控制权是反过来的:Post::deserialize 只是把一个 Visitor 交出去,读什么、读到哪一步由格式决定,类型在 visit_map 里被一个个键值回调。deserialize_struct 这个方法名只是一个提示:自描述的格式(JSON)不看它也能读,非自描述的格式(bincode)全靠它。
缓冲主线:untagged、内部标签与 flatten
flowchart TB gen["生成的 deserialize"] --> cv["ContentVisitor"] cv --> hook["__deserialize_content_v1"] hook --> anyde["默认转给 deserialize_any"] anyde --> tree["整段输入变成 Content 树"] tree --> replay["ContentRefDeserializer"] replay --> v1["试第一个变体"] v1 -->|失败| v2["试下一个变体"] v1 -->|成功| okv[返回] v2 -->|全失败| errv["data did not match ..."]
| 步骤 | 源码 | 讲它的页 |
|---|---|---|
1. #[serde(untagged)] 的 enum:生成的代码先 DeserializeSeed::deserialize(ContentVisitor::new(), __deserializer)?,把输入读成 __content,再用 ContentRefDeserializer::new(&__content) 逐个变体去试 | enum_untagged.rs 的 deserialize | untagged |
2. ContentVisitor 作为 seed 时调 Deserializer 上的隐藏方法 __deserialize_content_v1,它的默认实现就是 deserialize_any:格式不给提示也得说出「这里是什么」 | serde/src/private/de.rs、serde_core/src/de/mod.rs | Content:缓冲的代价 |
3. Content<'de> 是一棵树:Bool、U8…U64、I8…I64、F32、F64、Char、String / Str、ByteBuf / Bytes、None / Some、Unit、Newtype、Seq、Map,没有 u128 / i128 | serde_core/src/private/content.rs | 同上 |
4. 重放:ContentRefDeserializer(借用 Content,可以试多次)和 ContentDeserializer(拿走所有权)本身是 Deserializer,对这棵树再跑一遍反序列化主线 | serde/src/private/de.rs | 同上 |
5. 全部失败时只报一句 data did not match any variant of untagged enum X:每个变体为什么失败的信息被丢掉了,enum_untagged.rs 里的 TODO 注释承认了这一点 | 同 1 | untagged |
6. 内部标签 #[serde(tag = "type")]:TaggedContentVisitor 一边找标签一边把其余键值缓冲成 Content | enum_internally.rs | 内部标签与相邻标签 |
7. flatten:序列化一侧改用 serialize_map,由 FlatMapSerializer 把内层字段摊进外层;反序列化一侧把没认出的键值都缓冲下来,再用 FlatMapDeserializer 交给每个 flatten 字段 | serde/src/private/ser.rs、de.rs | flatten |
缓冲主线是反序列化主线的一个变形:中间多出一棵树,反序列化跑两遍。第二遍面对的是 Content 而不是原始输入,所以格式的类型提示没有了,只剩 deserialize_any 当时给出的信息。bincode、postcard 这类非自描述格式不支持 deserialize_any,也就用不了这三种写法。
怎么看生成代码
derive 生成的代码不在任何源文件里,要用 cargo expand(本机装的是 cargo-expand 1.0.104):
cargo expand --bin post
src/bin/post.rs 就是上面的 Post 加一个 main:
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Post {
title: String,
tags: Vec<String>,
}
fn main() {
let post = Post { title: "serde".to_string(), tags: vec!["rust".to_string()] };
let json = serde_json::to_string(&post).unwrap();
println!("{json}");
let back: Post = serde_json::from_str(&json).unwrap();
println!("{back:?}");
}
运行输出:
{"title":"serde","tags":["rust"]}
Post { title: "serde", tags: ["rust"] }
展开结果一共 305 行。Serialize 那一半很短,几乎原样摘出:
#[doc(hidden)]
#[allow(
non_upper_case_globals,
unused_attributes,
unused_qualifications,
clippy::absolute_paths,
)]
const _: () = {
#[allow(unused_extern_crates, clippy::useless_attribute)]
extern crate serde as _serde;
#[automatically_derived]
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 + 1,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"title",
&self.title,
)?;
_serde::ser::SerializeStruct::serialize_field(
&mut __serde_state,
"tags",
&self.tags,
)?;
_serde::ser::SerializeStruct::end(__serde_state)
}
}
};
读生成代码时留意几处:
- 整个 impl 包在
const _: () = { ... };里,里面extern crate serde as _serde;,之后一律写_serde::...的全路径。这样生成的代码不依赖用户在这个模块里use了什么,也不往用户的命名空间里放任何名字。包装由dummy.rs生成,见派生宏的流水线。 dummy.rs在extern crate之后还会插一行_serde::__require_serde_not_serde_core!();,展开结果里看不到,因为serde里这个宏展开成空;它的作用见源码地图。- 标准库的类型和函数都经
_serde::__private229::取(Result、Option、PhantomData、Formatter),derive 专用的运行时函数也在这里(_serde::__private229::de::missing_field)。 - 字段数写成
false as usize + 1 + 1:每个字段贡献一个+ 1,开头的false as usize是给skip_serializing_if留的位置。前面shapes.rs里的Post带着skip_serializing_if,展开成false as usize + 1 + if Vec::is_empty(&self.tags) { 0 } else { 1 }。见生成的 Serialize。
问: 上一节说 1.0.229 去掉了生成代码里的 ?,为什么这里的 Serialize 每一步后面还是 ??
答: #2936 只改了三个地方:外部标签 enum 的 visit_enum、相邻标签 enum 的 visit_seq、deserialize_in_place 里的 visit_seq。它们的共同点是 ? 的结果紧接着就被 match,把 ? 并进 match 只是多写一个 Err 分支。像 serialize_field(...)?; 这样一行一个调用的地方,要去掉 ? 就得把每一行都改写成一个 match,提交没有动它们,展开结果也证明了这一点:前面那次 diff 里三个 Serialize impl 一行都没变。
Deserialize 那一半 200 多行,摘出主线上的几段。deserialize 本身只调一次 deserialize_struct:
#[doc(hidden)]
const FIELDS: &'static [&'static str] = &["title", "tags"];
_serde::Deserializer::deserialize_struct(
__deserializer,
"Post",
FIELDS,
__Visitor {
marker: _serde::__private229::PhantomData::<Post>,
lifetime: _serde::__private229::PhantomData,
},
)
__FieldVisitor 用三种方式认字段:visit_u64(字段序号)、visit_str、visit_bytes,认不出的一律是 __ignore:
fn visit_str<__E>(
self,
__value: &str,
) -> _serde::__private229::Result<Self::Value, __E>
where
__E: _serde::de::Error,
{
match __value {
"title" => _serde::__private229::Ok(__Field::__field0),
"tags" => _serde::__private229::Ok(__Field::__field1),
_ => _serde::__private229::Ok(__Field::__ignore),
}
}
__Visitor::visit_map 是反序列化主线的第 5 到第 7 步:
let mut __field0: _serde::__private229::Option<String> = _serde::__private229::None;
let mut __field1: _serde::__private229::Option<Vec<String>> = _serde::__private229::None;
while let _serde::__private229::Some(__key) = _serde::de::MapAccess::next_key::<
__Field,
>(&mut __map)? {
match __key {
__Field::__field0 => {
if _serde::__private229::Option::is_some(&__field0) {
return _serde::__private229::Err(
<__A::Error as _serde::de::Error>::duplicate_field("title"),
);
}
__field0 = _serde::__private229::Some(
_serde::de::MapAccess::next_value::<String>(&mut __map)?,
);
}
// ... __field1 同样
_ => {
let _ = _serde::de::MapAccess::next_value::<
_serde::de::IgnoredAny,
>(&mut __map)?;
}
}
}
let __field0 = match __field0 {
_serde::__private229::Some(__field0) => __field0,
_serde::__private229::None => {
_serde::__private229::de::missing_field("title")?
}
};
// ...
同一个 __Visitor 还实现了 visit_seq,按顺序取 next_element,所以 JSON 数组 ["serde", ["rust"]] 也能读成 Post。这些细节在生成的 Deserialize 里逐段讲。
本卷引用生成代码时都这样摘:只留关键几行,删掉的部分用 // ... 标出,_serde::__private229::... 这样的私有路径原样保留,好和自己机器上的 cargo expand 对照。
阅读路线
- 只想会用:读 01 全景、03 里的属性、04 enum 的表示与缓冲和 07 常见坑。读完能回答:
#[serde(...)]的哪个属性改的是哪一步、四种 enum 表示在 JSON 里长什么样、为什么 untagged 的报错只有一句话、为什么某些写法在 bincode 上不行。 - 想读懂实现:按顺序读。02 是重点,前两条主线都从那里的 trait 出发;03 从类型一侧往下走,看 derive 生成了什么;05 从格式一侧往下走,看 serde_json 怎么实现这些 trait;04 是缓冲主线;06 把各章的取舍汇总。
- 要写一个格式:02 全部,然后 05;04 的 Content 讲清楚了你的
deserialize_any会被谁调用。 - 要手写
Serialize/Deserialize:02 的前三章、value 模块与 IntoDeserializer,再对照 03 的生成代码。
前置知识
这一卷要求会写 Rust。下面几样东西在正文里反复出现:
| 概念 | 本卷用到它的地方 |
|---|---|
| 泛型与单态化 | to_string::<Post> 编译成一份专门的代码;Serializer: Sized,又有一堆泛型方法,所以不能做成 trait 对象 |
| 关联类型 | Serializer 有 Ok、Error 和七个复合类型的关联类型(SerializeSeq … SerializeStructVariant);Visitor::Value |
| 生命周期与 HRTB | Deserialize<'de> 的 'de;DeserializeOwned 是 for<'de> Deserialize<'de> |
过程宏、syn 与 quote | serde_derive 用 syn 解析 DeriveInput,用 quote! 拼出代码;1.0.229 用的是 syn 3 |
cfg 与 Cargo feature | std、alloc、rc、derive、unstable、result,见源码地图 |
| axum 的提取器 | body 提取器、Path、Query 与请求元数据:本站的请求体和查询串就是从那里交给 serde 的 |
实验环境
关于行为的说法(调了哪个方法、调了几次、按什么顺序、报什么错、写出什么 JSON、rustc 说了什么)都配一个小程序和它的真实输出。
- 平台:M2 Max、macOS、rustc 1.99.0。serde 没有平台相关的代码路径,不在 Linux 上重跑。
- 依赖版本用
=锁死:serde = { version = "=1.0.229", features = ["derive"] }、serde_json = "=1.0.154"。用到别的格式时一并写出版本(bincode 1.3.3、postcard 1、serde_yaml 0.9.34、serde_urlencoded 0.7.1、toml 0.8)。 - serde_json 的
arbitrary_precision、preserve_order、raw_value、unbounded_depth这几个 feature 会被 Cargo 合并到整个依赖图,用到它们的实验单独建一个 crate,不和默认 feature 的实验混在一起。 - 「哪个方法被调了、调了几次」这类说法,用一个打印调用的手写
Serializer、Deserializer或Visitor证明,不凭读代码推断。 - 编译错误一律引 rustc 的原文。
本卷的约定
- 每页开头的「主线位置」说明本页在序列化主线、反序列化主线、缓冲主线上的哪一步,还是都不在(比如源码地图)。
- 摘自 serde 和 serde_json 的代码片段一律保留原来的格式(4 空格缩进),删掉的行用
// ...标出;本站自己写的示例用 2 空格缩进。 - 「格式」指实现
Serializer/Deserializer的 crate(serde_json、bincode……);「类型」指实现Serialize/Deserialize的 Rust 类型。「数据模型」指 serde 规定的 29 种类型。 - 报错信息照源码和实际输出原样引用,不翻译。
目录
| 部分 | 内容 |
|---|---|
| 00 导读 | 本页;源码地图 |
| 01 全景 | serde 解决什么问题;一次往返 |
| 02 核心 trait | 本卷重点:Serializer、Deserializer 与 Visitor、三个 Access trait、'de、错误、标准库类型、value 模块 |
| 03 派生宏 | 流水线、属性、生成的 Serialize、生成的 Deserialize、泛型与 bound |
| 04 enum 的表示与缓冲 | 外部标签、内部与相邻标签、untagged、Content、flatten |
| 05 一个格式:serde_json | 序列化一侧、反序列化一侧、Value |
| 06 设计取舍 | 把各章的取舍汇总到一起 |
| 07 常见坑 | 用报错原文做标题,每个都有最小复现 |
| 08 在本站中 | 本站怎么用 serde;1.0.228 / 1.0.150 到本卷版本的改动对本站的影响 |
| 09 动手 | 不依赖 serde,从零写一个迷你 serde |
小结
- 全卷对着 serde
v1.0.229(7fc3b4c,2026-07-18)和 serde_jsonv1.0.154(20572a6,2026-10-11)读;本站锁的是 1.0.228 和 1.0.150。 - 1.0.228 → 1.0.229:syn 3、
serde_derive的最低编译器升到 1.71、serde_derive自身的 llvm-lines 优化,都不改生成代码;真正改了生成代码的只有 #2936,把三处match x? {...}改成带Err分支的match。同一个文件用两个版本展开,除__private228/__private229外只差 46 行,运行输出相同。 - serde_json 1.0.150 → 1.0.154 的五项改动:
RawValue::from_string_unchecked、arbitrary_precision下的-0、Value == f32的比较、非法 UTF-8 的报错位置、no_std 下的preserve_order。只有报错位置一项在默认的读路径上,而且只改输入含非法 UTF-8 时报出的列号。 - 序列化主线:
to_string→Post::serialize→serialize_struct写{→ 每个字段serialize_field(Formatter决定逗号)→end写}。类型推,格式被调。 - 反序列化主线:
from_str→Post::deserialize→deserialize_struct看到{→visit_map里next_key/next_value→missing_field→end检查尾部。格式驱动,类型被回调。 - 缓冲主线:
ContentVisitor经__deserialize_content_v1(默认即deserialize_any)把输入读成Content,再用ContentRefDeserializer重放;丢掉类型提示、u128和失败原因。