Rust in Everything
基于 serde 1.0.229

生成的 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 两次都没出现。

📝NOTE
冷知识: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"unitserialize_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"structserialize_struct("Internal", 2),先 t 后字段{"t":"Rec","a":2}
tag = "t", content = "c"unitserialize_struct("Adjacent", 1),t 的值是 serialize_unit_variant{"t":"Unit"}
tag = "t", content = "c"newtypeserialize_struct("Adjacent", 2),t 同上,c 是内容{"t":"New","c":3}
untaggedunitserialize_unitnull
untaggednewtype内容自己的 serialize(serialize_u8)4
untaggedtupleserialize_tuple(2)[5,6]
untaggedstructserialize_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 = "...")] 那个字符串上,源码里的注释画了这个箭头。

📝NOTE
没有蠢问题

问: 生成代码明明能直接写 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。

❗IMPORTANT
动脑筋: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,格式看不到外层。

💬 关联讨论

发起讨论
加载中...