Rust in Everything
基于 serde 1.0.229

怎么读这一卷

主线位置:三条主线的总览。本页把序列化主线、反序列化主线和缓冲主线各画一遍,标出每一步落在哪个函数上、哪一页讲它;下一部分的「一次往返」用一个完整的例子把前两条线走一遍,并用打印调用的手写 Serializer 和 Visitor 证明顺序。

flowchart TB
  start[从这里开始] --> q{读这一卷为了什么}
  q -->|会用| use1[01 全景]
  use1 --> use2[03 的属性]
  use2 --> use3["04 enum 的表示"]
  use3 --> use4[07 常见坑]
  q -->|读懂实现| all[00 到 09 按顺序]

锁定版本

项serdeserde_json
版本1.0.229(serde、serde_core、serde_derive 同版本)1.0.154
tagv1.0.229v1.0.154
commit7fc3b4c(2026-07-18,「Release 1.0.229」)20572a6(2026-10-11,「Release 1.0.154」)
rust-versionserde、serde_core 1.56;serde_derive 1.711.71
本站 Cargo.lock1.0.2281.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、681270e41.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!cfe66924visit_seq(mut self, ...) 里的 &mut self.0 改成 &mut *self.0,消掉 clippy 的 mut_mut不变
serde 的 unstablefa7da4a9#![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#1346arbitrary_precision 下 -0 不再被解析成 i64 的 0,而是原样留成字符串 "-0",负号得以保留开 arbitrary_precision
1.0.152#1347Number::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#1333preserve_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,最后 endexpand_derive_serialize、serialize_struct_as_struct生成的 Serialize
2. 这些调用都是 trait 方法:Serialize 只有一个 serialize<S: Serializer>;Serializer 的 serialize_struct 返回一个实现 SerializeStruct 的状态对象serde_core/src/ser/mod.rsSerialize 与 Serializer
3. to_string → to_vec(先开 128 字节的 Vec)→ to_writer:Serializer::new(writer),然后 value.serialize(&mut ser)serde_json ser.rsserde_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.rsDeserialize、Deserializer 与 Visitor
3. from_str 用 StrRead 包住输入,交给 from_trait:Deserializer::new、Deserialize::deserialize(&mut de),最后 end 检查后面只剩空白,否则报 TrailingCharactersserde_json de.rsserde_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同上、MapAccessSeqAccess、MapAccess 与 EnumAccess
6. 按字段 next_value::<T>()(serde_json 的 next_value_seed 先吃掉 :);同一字段第二次出现报 duplicate_field,没认出的键读成 IgnoredAny 扔掉同上生成的 Deserialize
7. 循环结束后,还是 None 的字段交给 missing_field:类型是 Option<T> 时得到 None,否则报 missing fieldserde/src/private/de.rs同上
8. Deserialize<'de> 的 'de 是输入的生命周期:visit_borrowed_str 让 &'de str 字段直接指向输入,有转义时只能 visit_strserde_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 的 deserializeuntagged
2. ContentVisitor 作为 seed 时调 Deserializer 上的隐藏方法 __deserialize_content_v1,它的默认实现就是 deserialize_any:格式不给提示也得说出「这里是什么」serde/src/private/de.rs、serde_core/src/de/mod.rsContent:缓冲的代价
3. Content<'de> 是一棵树:Bool、U8…U64、I8…I64、F32、F64、Char、String / Str、ByteBuf / Bytes、None / Some、Unit、Newtype、Seq、Map,没有 u128 / i128serde_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 注释承认了这一点同 1untagged
6. 内部标签 #[serde(tag = "type")]:TaggedContentVisitor 一边找标签一边把其余键值缓冲成 Contentenum_internally.rs内部标签与相邻标签
7. flatten:序列化一侧改用 serialize_map,由 FlatMapSerializer 把内层字段摊进外层;反序列化一侧把没认出的键值都缓冲下来,再用 FlatMapDeserializer 交给每个 flatten 字段serde/src/private/ser.rs、de.rsflatten

缓冲主线是反序列化主线的一个变形:中间多出一棵树,反序列化跑两遍。第二遍面对的是 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。
📝NOTE
没有蠢问题

问: 上一节说 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
生命周期与 HRTBDeserialize<'de> 的 'de;DeserializeOwned 是 for<'de> Deserialize<'de>
过程宏、syn 与 quoteserde_derive 用 syn 解析 DeriveInput,用 quote! 拼出代码;1.0.229 用的是 syn 3
cfg 与 Cargo featurestd、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_json v1.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 和失败原因。

💬 关联讨论

发起讨论
加载中...