Rust in Everything
基于 serde 1.0.229

内部标签与相邻标签

主线位置:缓冲主线的一支。内部标签 #[serde(tag = "type")] 的反序列化先用 TaggedContentVisitor 把整个对象读一遍,拿出标签,其余键值缓冲成 Content,再用 ContentDeserializer 对选中的变体重放一次反序列化主线。相邻标签 #[serde(tag = "t", content = "c")] 只在内容出现在标签之前时才缓冲。序列化一侧两者都不缓冲:内部标签靠 TaggedSerializer 把标签插进内容的第一个字段,相邻标签就是一个两字段的 struct。

flowchart TB
  gen["生成的 deserialize"] --> tcv["TaggedContentVisitor"]
  tcv --> loopk{下一个键}
  loopk -->|"是 type"| tagv["读标签值 __Field"]
  loopk -->|其他键| buf["键值读成 Content"]
  tagv --> loopk
  buf --> loopk
  loopk -->|没有了| pair["返回 标签 和 Content"]
  pair --> cd["ContentDeserializer"]
  cd --> arm["按标签选一个变体重放"]

外部标签(默认表示)不缓冲,读法见外部标签;Content 这棵树本身、它丢掉了哪些信息、ContentDeserializer 怎么把 deserialize_u32 映射到 Content::U64,见 Content:缓冲的代价。这一页只讲两种带标签的表示怎么用上它。本页实验都在 M2 Max、macOS、rustc 1.99.0 上运行,依赖 serde 1.0.229、serde_json 1.0.154。

两种表示长什么样

同一个变体 Data { id: 1 }:

表示属性JSON
外部标签无{"Data":{"id":1}}
内部标签#[serde(tag = "type")]{"type":"Data","id":1}
相邻标签#[serde(tag = "t", content = "c")]{"t":"Data","c":{"id":1}}

内部标签把标签和内容的字段放在同一个对象里,读的时候不知道标签在第几个键。相邻标签把两者分开放在两个键里,读到哪一个先都能分辨。这一点差别决定了两者缓冲的方式。

内部标签:生成的代码

enum_internally.rs 的 deserialize 生成的函数体只有三步。下面是 cargo expand 的输出,enum 有 Ping(unit)、Point(Point)(newtype)、Data { id: u32 }(struct)等变体:

let (__tag, __content) = _serde::Deserializer::deserialize_any(
    __deserializer,
    _serde::__private229::de::TaggedContentVisitor::<
        __Field,
    >::new("type", "internally tagged enum Msg"),
)?;
let __deserializer = _serde::__private229::de::ContentDeserializer::<
    __D::Error,
>::new(__content);
match __tag {
    __Field::__field0 => {
        _serde::Deserializer::deserialize_any(
            __deserializer,
            _serde::__private229::de::InternallyTaggedUnitVisitor::new(
                "Msg",
                "Ping",
            ),
        )?;
        _serde::__private229::Ok(Msg::Ping)
    }
    __Field::__field1 => {
        _serde::__private229::Result::map(
            <Point as _serde::Deserialize>::deserialize(__deserializer),
            Msg::Point,
        )
    }
    // ...
}
  1. 对格式调 deserialize_any,Visitor 是 TaggedContentVisitor<__Field>,得到 (__tag, __content)。这里直接调 deserialize_any,不经过 ContentVisitor 用的 __deserialize_content_v1;对象里每个值才经 ContentVisitor 读成 Content。
  2. 把 __content 交给 ContentDeserializer,并且遮蔽了原来的 __deserializer:后面的代码以为自己面对的还是一个格式。
  3. 按标签 match。newtype 变体就是 Point::deserialize(__deserializer),struct 变体是一段完整的 struct 反序列化代码(StructForm::InternallyTagged,用 deserialize_any),unit 变体见后面的 InternallyTaggedUnitVisitor。

这里用的是拿走所有权的 ContentDeserializer,不是 untagged 用的 ContentRefDeserializer:标签已经定了只试一个变体,Content 用一次就可以扔掉,String 之类可以直接移交,不用再克隆。untagged 要拿同一份 Content 试好几次,只能借用,见 untagged。

TaggedContentVisitor:标签之外全部缓冲

TaggedContentVisitor 的 Value 是 (T, Content<'de>)。它的 visit_map:

        fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
        where
            M: MapAccess<'de>,
        {
            let mut tag = None;
            let mut vec = Vec::<(Content, Content)>::with_capacity(size_hint::cautious::<(
                Content,
                Content,
            )>(map.size_hint()));
            while let Some(k) = tri!(map.next_key_seed(TagOrContentVisitor::new(self.tag_name))) {
                match k {
                    TagOrContent::Tag => {
                        if tag.is_some() {
                            return Err(de::Error::duplicate_field(self.tag_name));
                        }
                        tag = Some(tri!(map.next_value()));
                    }
                    TagOrContent::Content(k) => {
                        let v = tri!(map.next_value_seed(ContentVisitor::new()));
                        vec.push((k, v));
                    }
                }
            }
            match tag {
                None => Err(de::Error::missing_field(self.tag_name)),
                Some(tag) => Ok((tag, Content::Map(vec))),
            }
        }
  • 每个键都先过一遍 TagOrContentVisitor。 它作为 seed 调 deserialize_any(注释是「Internally tagged enums are only supported in self-describing formats.」),在 visit_str / visit_borrowed_str / visit_string / visit_bytes 等方法里把键和标签名比较:相等返回 TagOrContent::Tag,否则把键本身交给 ContentVisitor 变成 Content。键不一定是字符串,visit_bool、visit_u64 这些也都实现了,直接存成 Content。
  • 标签的值不进 Content。 map.next_value() 的类型是 T,也就是生成的 __Field,直接从格式读,走 deserialize_identifier。
  • 其余的值进 Content。 next_value_seed(ContentVisitor::new()),整棵子树都读进来。
  • 循环不会在找到标签后停下。 标签前面的键值、后面的键值都推进同一个 vec,最后包成 Content::Map(vec)。这个 map 里没有标签那一项。
  • 标签出现两次报 duplicate_field,没有标签报 missing_field。

用 一次往返 里那个打印调用的 LogDe 包住 serde_json 的 Deserializer,看第一遍读了什么;再给变体里的类型套一层 Logged<T>,它的 Deserialize 先打印自己拿到的 Deserializer 类型名,再用 LogDe 包住它往下读,这样能看到第二遍(重放)。src/bin/internal_log.rs:

#[derive(Deserialize, Debug)]
struct Inner {
  id: u32,
  name: String,
}

#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
enum Msg {
  Ping,
  Data(Logged<Inner>),
}

fn run(json: &str) {
  println!("== {json}");
  let mut de = serde_json::Deserializer::from_str(json);
  let r = Msg::deserialize(LogDe(&mut de));
  println!("=> {r:?}");
}

fn main() {
  run(r#"{"id":1,"type":"Data","name":"x"}"#);
}

标签故意放在中间:

== {"id":1,"type":"Data","name":"x"}
deserialize_any
  visit_map
    next_key_seed
      deserialize_any
        visit_borrowed_str("id")
    next_value_seed
      deserialize_any
        visit_u64(1)
    next_key_seed
      deserialize_any
        visit_borrowed_str("type")
    next_value_seed
      deserialize_identifier
        visit_borrowed_str("Data")
    next_key_seed
      deserialize_any
        visit_borrowed_str("name")
    next_value_seed
      deserialize_any
        visit_borrowed_str("x")
    next_key_seed
      => None
[Inner gets serde::private::de::content::ContentDeserializer]
  deserialize_struct("Inner", ["id", "name"])
    visit_map
      next_key_seed
        deserialize_identifier
          visit_borrowed_str("id")
      next_value_seed
        deserialize_u32
          visit_u64(1)
      next_key_seed
        deserialize_identifier
          visit_borrowed_str("name")
      next_value_seed
        deserialize_string
          visit_borrowed_str("x")
      next_key_seed
        => None
=> Ok(Data(Logged(Inner { id: 1, name: "x" })))
  • 第一遍只有一个 deserialize_any。三个键都用 deserialize_any 读;"id" 和 "name" 的值也是 deserialize_any(ContentVisitor 走 __deserialize_content_v1,LogDe 没覆盖它,默认实现转给 deserialize_any,serde_json 也没覆盖)。只有标签的值用了 deserialize_identifier。
  • "id" 在标签之前,照样被读成 Content;"name" 在标签之后,也一样。
  • 第二遍里 Inner 拿到的是 ContentDeserializer,它看到的 map 只有 id 和 name 两个键。Inner 给出了 deserialize_u32 的提示,Content 里存的是 U64,回调的还是 visit_u64。字符串在第一遍是借用的 &'de str,重放时仍是 visit_borrowed_str。
📝NOTE
没有蠢问题

问: 标签要是第一个键,读到它就知道是哪个变体了,为什么还要把后面的键值缓冲起来?

答: 因为 TaggedContentVisitor 是一个和具体变体无关的泛型 Visitor:它只知道标签的类型 T(生成的 __Field),手里没有任何变体的 Visitor,读到标签以后也没法把剩下的 MapAccess 交给某个变体去读,只能把剩下的东西装成一个值还给生成的代码,这个值就是 Content。下面的相邻标签不同,它的 visit_map 是 derive 为这个 enum 现生成的,生成时就知道所有变体,所以读到标签以后能直接用 __Seed 从格式读内容。后面的分配计数表也说明了这一点:内部标签不管标签在前在后,分配次数一样。

输入是数组的时候

visit_seq 把第一个元素当标签,剩下的 SeqAccess 用公开的 de::value::SeqAccessDeserializer 包起来,交给 ContentVisitor 读成 Content(见 value 模块):

        fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
        where
            S: SeqAccess<'de>,
        {
            let tag = match tri!(seq.next_element()) {
                Some(tag) => tag,
                None => {
                    return Err(de::Error::missing_field(self.tag_name));
                }
            };
            let rest = de::value::SeqAccessDeserializer::new(seq);
            Ok((tag, tri!(ContentVisitor::new().deserialize(rest))))
        }

所以 ["Data", 7] 也能读成 Data { id: 7 }:struct 变体生成的 __Visitor 有 visit_seq,按位置取字段。

📝NOTE
冷知识:数组形式是给 MessagePack 加的

visit_seq 是 PR #1058「Support deserializing internally tagged enum from seq」加的,首次发布在 1.0.15。说明里写:「During serialization, internally tagged enums invoke the Serializer's serialize_struct. In JSON this turns into a map which uses visit_map when deserialized. But some formats employ visit_seq when deserializing a struct. One example is rmp-serde. Such formats were previously unable to deserialize an internally tagged enum.」在这之前 TaggedContentVisitor 只有 visit_map。1.0.15 的发布说明是「Support for deserializing internally tagged enums in MessagePack (#1058)」。

实验:几种输入

src/bin/internal_de.rs,enum 是:

#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
enum Msg {
  Ping,
  Point(Point),   // struct Point { x: i32, y: i32 }
  Shape(Shape),   // 外部标签 enum Shape { Circle(f64) }
  Unit(()),
  Data { id: u32 },
}

每个输入调一次 serde_json::from_str::<Msg>:

{"type":"Ping"}                          => Ok(Ping)
{"type":"Ping","extra":[1,2]}            => Ok(Ping)
{"x":1,"y":2,"type":"Point"}             => Ok(Point(Point { x: 1, y: 2 }))
{"type":"Shape","Circle":1.5}            => Ok(Shape(Circle(1.5)))
{"type":"Unit"}                          => Ok(Unit(()))
["Data", 7]                              => Ok(Data { id: 7 })
["Ping"]                                 => Ok(Ping)
["Ping", 1]                              => Err(Error("invalid length 1, expected 0 elements in sequence", line: 0, column: 0))
{"id":7}                                 => Err(Error("missing field `type`", line: 1, column: 8))
{"type":"Data","type":"Data","id":7}     => Err(Error("duplicate field `type`", line: 1, column: 21))
{"type":"Nope"}                          => Err(Error("unknown variant `Nope`, expected one of `Ping`, `Point`, `Shape`, `Unit`, `Data`", line: 1, column: 14))
"Ping"                                   => Err(Error("invalid type: string \"Ping\", expected internally tagged enum Msg", line: 1, column: 6))
  • unit 变体容忍多余的键。 {"type":"Ping","extra":[1,2]} 成功了。unit 变体的重放用 InternallyTaggedUnitVisitor,它的 visit_map 用 next_entry::<IgnoredAny, IgnoredAny>() 把剩下的键值全部跳过,visit_seq 直接返回 Ok(())。
  • 数组形式的 unit 变体不容忍多余元素。 ["Ping", 1] 失败了:visit_seq 没有取元素就返回,ContentDeserializer 重放完一个 Content::Seq 之后检查有没有剩下没读的元素,剩一个,报 invalid length。
  • 重放阶段的错误没有位置。 ["Ping", 1] 的错误是 line: 0, column: 0,而 missing field、duplicate field、unknown variant 都带位置:前三个是在第一遍、serde_json 的 deserialize_any 里报的,serde_json 返回前用 fix_position 补上了位置;invalid length 是 ContentDeserializer 在第二遍报的,那时 serde_json 早已返回,没人再补(line == 0 表示没有位置,见错误)。
  • newtype 变体里可以是 map 形状的任何东西。 Shape(Shape) 包的是一个外部标签 enum,它在对象里占一个键 "Circle";Unit(()) 包的是 (),重放时面对的是一个空的 Content::Map,也能读。
  • 输入不是对象也不是数组时,TaggedContentVisitor 的 expecting 给出「internally tagged enum Msg」,这个字符串就是生成代码里 new("type", "internally tagged enum Msg") 的第二个参数,可以用容器属性 #[serde(expecting = "...")] 换掉(见属性)。

序列化一侧:TaggedSerializer

序列化不需要缓冲:标签在写第一个字段之前就知道。serialize_internally_tagged_variant 按变体形状分三种:

  • unit 变体:serialize_struct(type_name, 1),写一个字段 tag: variant_name。
  • struct 变体:照常 serialize_struct,长度加一,第一个字段是标签。
  • newtype 变体:调 serialize_tagged_newtype,把内层的值交给一个包着真正格式的 TaggedSerializer。

newtype 变体的难处在于:derive 生成代码时只看到字段的类型名,不知道它序列化出来是什么形状。TaggedSerializer 于是在运行期等内层的值自己说:它调哪个 serialize_*,就在哪里插标签。插得进去的只有 map 和 struct:

    fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
        let mut map = tri!(self.delegate.serialize_map(len.map(|len| len + 1)));
        tri!(map.serialize_entry(self.tag, self.variant_name));
        Ok(map)
    }

    fn serialize_struct(
        self,
        name: &'static str,
        len: usize,
    ) -> Result<Self::SerializeStruct, Self::Error> {
        let mut state = tri!(self.delegate.serialize_struct(name, len + 1));
        tri!(state.serialize_field(self.tag, self.variant_name));
        Ok(state)
    }

serialize_map 和 serialize_struct 先开一个长度加一的 map / struct,写进标签,再把 SerializeMap / SerializeStruct 原样交还给内层,后面的字段由内层自己写。serialize_unit 和 serialize_unit_struct 写一个只有标签的 map;serialize_newtype_struct 剥掉一层继续往下;内层是 enum 时,unit、newtype、tuple、struct 四种变体都写成 map,标签之后再加一个以变体名为键的项(后两种在 alloc 下才有,因为要先缓冲内容)。

其余的方法都返回 bad_type 造的错误:

    fn bad_type(self, what: Unsupported) -> S::Error {
        ser::Error::custom(format_args!(
            "cannot serialize tagged newtype variant {}::{} containing {}",
            self.type_ident, self.variant_ident, what
        ))
    }

Unsupported 是一个只为这句话存在的枚举,Display 给出「a boolean」「an integer」「a float」「a char」「a string」「a byte array」「an optional」「a sequence」「a tuple」「a tuple struct」。

src/bin/internal_ser.rs:

#[derive(Serialize)]
#[serde(tag = "type")]
enum Msg {
  Ping,
  Move { dx: i32 },
  Point(Point),
  Map(BTreeMap<String, u32>),
  Unit(()),
  Num(u32),
  Text(String),
  List(Vec<u32>),
}

逐个 serde_json::to_string:

ok:  {"type":"Ping"}
ok:  {"type":"Move","dx":3}
ok:  {"type":"Point","x":1,"y":2}
ok:  {"type":"Map","a":1}
ok:  {"type":"Unit"}
err: cannot serialize tagged newtype variant Msg::Num containing an integer
err: cannot serialize tagged newtype variant Msg::Text containing a string
err: cannot serialize tagged newtype variant Msg::List containing a sequence

Num(u32)、Text(String)、List(Vec<u32>) 都编译通过了,错误到运行期才出现:一个整数、一个字符串、一个数组没有地方放 "type" 这个键。这条限制从内部标签诞生时就有,PR #739(0.9.6)的说明写的是「newtype variants must contain either a struct or a map and are serialized with "type": "T" inserted as the first field」。

tuple 变体则在编译期就被拒绝。检查在 attr.rs 的 decide_tag 里,只看字段个数(Unnamed 且不是 1 个字段):

#[derive(Serialize)]
#[serde(tag = "type")]
enum Msg {
  Pair(u32, u32),
}
error: #[serde(tag = "...")] cannot be used with tuple variants
 --> src/bin/tuple_variant_err.rs:6:3
  |
6 |   Pair(u32, u32),
  |   ^^^^^^^^^^^^^^
❗IMPORTANT
动脑筋:Option 和 enum 能不能放进内部标签的 newtype 变体?

平台 M2 Max、macOS、rustc 1.99.0。src/bin/internal_quiz.rs:

#[derive(Serialize)]
enum Shape {
  Circle(f64),
}

#[derive(Serialize)]
#[serde(tag = "type")]
enum Msg {
  Maybe(Option<Point>),  // struct Point { x: i32, y: i32 }
  Shape(Shape),
}

fn main() {
  let a = serde_json::to_string(&Msg::Maybe(Some(Point { x: 1, y: 2 })));
  let b = serde_json::to_string(&Msg::Shape(Shape::Circle(1.5)));
  println!("{a:?}");
  println!("{b:?}");
}

Some(Point) 里面明明是一个 struct。两行各打印什么?

相邻标签:一个两字段的 struct

相邻标签的序列化最简单:serialize_adjacently_tagged_variant 生成 serialize_struct(type_name, 2),字段 t 是标签,字段 c 是内容(unit 变体只有 t,长度是 1)。内容是什么形状都行,Num(u32) 写成 {"t":"Num","c":7},没有内部标签的限制,tuple 变体也可以。

反序列化一侧,enum_adjacently.rs 的 deserialize 生成的入口是 deserialize_struct(type_name, &[tag, content], __Visitor),不是 deserialize_any。__Visitor 是为这个 enum 现生成的,它的 visit_map 看第一个有用的键是哪个:

                match #next_relevant_key {
                    // First key is the tag.
                    _serde::#private::Some(_serde::#private::de::TagOrContentField::Tag) => {
                        // Parse the tag.
                        let __field = #variant_from_map;
                        // Visit the second key.
                        match #next_relevant_key {
                            // ...
                            // Second key is the content.
                            _serde::#private::Some(_serde::#private::de::TagOrContentField::Content) => {
                                let __ret = _serde::de::MapAccess::next_value_seed(&mut __map,
                                    __Seed {
                                        variant: __field,
                                        marker: _serde::#private::PhantomData,
                                        lifetime: _serde::#private::PhantomData,
                                    })?;
                                // Visit remaining keys, looking for duplicates.
                                #visit_remaining_keys
                            }
                            // There is no second key; might be okay if the we have a unit variant.
                            _serde::#private::None => #missing_content
                        }
                    }
                    // First key is the content.
                    _serde::#private::Some(_serde::#private::de::TagOrContentField::Content) => {
                        // Buffer up the content.
                        let __content = _serde::de::MapAccess::next_value_seed(&mut __map, _serde::#private::de::ContentVisitor::new())?;
                        // Visit the second key.
                        match #next_relevant_key {
                            // Second key is the tag.
                            _serde::#private::Some(_serde::#private::de::TagOrContentField::Tag) => {
                                #finish_content_then_tag
                            }
                            // ...
  • 标签在前。 先用 #variant_from_map 读出标签,它是 next_value_seed(AdjacentlyTaggedEnumVariantSeed::<__Field> { .. })。再遇到内容键时,直接 next_value_seed(__Seed { variant: __field, .. }):__Seed 是生成的 DeserializeSeed,它按 variant 分派到各变体的读法(和 untagged 共用 enum_untagged::deserialize_variant),面对的是格式本身,没有缓冲。
  • 内容在前。 还不知道变体,只能 next_value_seed(ContentVisitor::new()) 把内容读成 Content,等读到标签后,#finish_content_then_tag 再用 ContentDeserializer::new(__content) 喂给 __Seed。
  • 其他键。 默认 #next_relevant_key 用 TagContentOtherFieldVisitor 认键,不认识的键读成 IgnoredAny 跳过;加了 deny_unknown_fields 换成 TagOrContentFieldVisitor,遇到第三种键直接报错。两者都走 deserialize_identifier。
  • 读完内容后 #visit_remaining_keys 继续看后面还有没有重复的标签或内容键。缺内容键时,unit 变体直接成功;newtype 变体交给 private::de::missing_field,内容类型是 Option<u32> 时得到 None,是 u32 时报 missing field `c` ;tuple、struct 变体直接报 missing field `c` 。

实验:标签在前、在后

同样用 Logged<Inner> 看变体内容是从谁那里读的。src/bin/adjacent_log.rs:

#[derive(Deserialize, Debug)]
#[serde(tag = "t", content = "c")]
enum Msg {
  Data(Logged<Inner>),   // struct Inner { id: u32, name: String }
}

fn main() {
  let first = r#"{"t":"Data","c":{"id":1,"name":"abcdef"}}"#;
  let last = r#"{"c":{"id":1,"name":"abcdef"},"t":"Data"}"#;
  for s in [first, last] {
    println!("== {s}");
    let r = serde_json::from_str::<Msg>(s);
    println!("=> {r:?}");
  }
  // ...
}
== {"t":"Data","c":{"id":1,"name":"abcdef"}}
[Inner gets &mut serde_json::de::Deserializer]
  deserialize_struct("Inner", ["id", "name"])
    visit_map
      next_key_seed
        deserialize_identifier
          visit_borrowed_str("id")
      next_value_seed
        deserialize_u32
          visit_u64(1)
      next_key_seed
        deserialize_identifier
          visit_borrowed_str("name")
      next_value_seed
        deserialize_string
          visit_borrowed_str("abcdef")
      next_key_seed
        => None
=> Ok(Data(Logged(Inner { id: 1, name: "abcdef" })))
== {"c":{"id":1,"name":"abcdef"},"t":"Data"}
[Inner gets serde::private::de::content::ContentDeserializer]
  deserialize_struct("Inner", ["id", "name"])
    visit_map
      next_key_seed
        deserialize_identifier
          visit_borrowed_str("id")
      next_value_seed
        deserialize_u32
          visit_u64(1)
      next_key_seed
        deserialize_identifier
          visit_borrowed_str("name")
      next_value_seed
        deserialize_string
          visit_borrowed_str("abcdef")
      next_key_seed
        => None
=> Ok(Data(Logged(Inner { id: 1, name: "abcdef" })))

两次的调用序列一模一样,区别只在第一行:标签在前时 Inner 拿到的是 &mut serde_json::de::Deserializer,在后时拿到的是 ContentDeserializer。

调用序列看不出代价,数分配次数能看出来。一个计数的 #[global_allocator] 在 from_str 前后各读一次计数器,同一个 Inner { id: 1, name: "abcdef" } 放进五种表示(src/bin/alloc_count.rs,内部标签和相邻标签用 t 作标签名):

struct           1  {"id":1,"name":"abcdef"}
external         1  {"Data":{"id":1,"name":"abcdef"}}
internal first   2  {"t":"Data","id":1,"name":"abcdef"}
internal last    2  {"id":1,"name":"abcdef","t":"Data"}
adjacent first   1  {"t":"Data","c":{"id":1,"name":"abcdef"}}
adjacent last    2  {"c":{"id":1,"name":"abcdef"},"t":"Data"}
untagged         2  {"id":1,"name":"abcdef"}
  • 那 1 次是 name: String 自己的分配,谁都省不掉。
  • 多出来的 1 次是 Content::Map 里的 Vec<(Content, Content)>。"abcdef" 没有转义,在 Content 里是借用的 Str,不另外分配。
  • 相邻标签在前和外部标签一样,只有 1 次:没有缓冲。
  • 内部标签在前在后都是 2 次:不论标签在哪,都缓冲。
📝NOTE
冷知识:相邻标签的标签以前是一个字符串

1.0.181 之前,相邻标签的标签用 serialize_field("t", "Variant") 写成字符串,读的时候却用 __Field 的 deserialize_identifier。issue #2496「Revisit the representation of variant id for adjacently tagged enums」指出这在按序号编码变体的格式里是错的:「In formats that serialize externally tagged enums using the variant number instead of name, deserialize_identifier involves reading a number. It doesn't make sense to serialize something as a str and deserialize as number.」PR #2505 改成了现在的样子:序列化写 AdjacentlyTaggedEnumVariant,它的 Serialize 是 serialize_unit_variant(enum_name, variant_index, variant_name);反序列化在 visit_map 里用 AdjacentlyTaggedEnumVariantSeed 调 deserialize_enum,再 unit_variant()。JSON 输出不变(PR 说明:「I've run some tests with serde_json and the output is the same」)。首次发布在 1.0.181,发布说明是「Improve the representation of adjacently tagged enums in formats where enum tags are serialized by index, as opposed to by string name (#2505, #2496)」。

相邻标签与 bincode

既然入口是 deserialize_struct,标签又按 unit 变体写,相邻标签看起来应该能用在 bincode 这种非自描述格式上。试一下(src/bin/binary_formats.rs,Inner { id: u32 },变体 A、B):

let bytes = bincode::serialize(&Adjacent::B(Inner { id: 7 })).unwrap();
println!("adjacent bincode bytes: {bytes:?}");
println!("adjacent bincode back:  {:?}", bincode::deserialize::<Adjacent>(&bytes));
// 内部标签、postcard 同理
adjacent bincode bytes: [1, 0, 0, 0, 7, 0, 0, 0]
adjacent bincode back:  Err(Custom("Bincode does not support Deserializer::deserialize_identifier"))
adjacent json:          {"t":"B","c":{"id":7}}
internal bincode bytes: [1, 0, 0, 0, 0, 0, 0, 0, 66, 7, 0, 0, 0]
internal bincode back:  Err(DeserializeAnyNotSupported)
adjacent postcard bytes: [1, 7]
adjacent postcard back:  Err(WontImplement)
internal postcard bytes: [1, 66, 7]
internal postcard back:  Err(WontImplement)
  • 序列化都成功了。相邻标签的字节是 [1, 0, 0, 0](变体序号 1,u32)加 [7, 0, 0, 0]:标签确实按序号写了。内部标签写的是字符串 "B"(长度 1 的 u64 加字节 66)。
  • 内部标签读不回来是预料之中的:deserialize_any。
  • 相邻标签读不回来,原因是另一条路。bincode 的 deserialize_struct 把 struct 当元组读,回调的是 visit_seq 而不是 visit_map。生成的 visit_seq 读第一个元素用的是 SeqAccess::next_element(&mut __seq),类型是 __Field,__Field 的 Deserialize 调 deserialize_identifier;bincode 1.3.3 和 postcard 1.1.3 都不实现这个方法。#2505 改的是 visit_map 里读标签的那一步,visit_seq 没有改用 AdjacentlyTaggedEnumVariantSeed。

所以在 serde 1.0.229 上,内部标签和相邻标签都只能和 bincode、postcard 单向配合:能写,读不回来。

动脑筋答案

Err(Error("cannot serialize tagged newtype variant Msg::Maybe containing an optional", line: 0, column: 0))
Ok("{\"type\":\"Shape\",\"Circle\":1.5}")
  • 第一行:Option<Point> 的 Serialize 先调 serialize_some,TaggedSerializer::serialize_some 不看里面是什么,直接 bad_type(Unsupported::Optional)。Some 里面的 Point 根本没有机会调 serialize_struct。
  • 第二行:内层是外部标签 enum,Shape::Circle(1.5) 调 serialize_newtype_variant,TaggedSerializer 开一个两项的 map,先写标签,再写 "Circle": 1.5。前面的 internal_de 实验里,同样的 JSON 也能读回 Shape(Circle(1.5))。

小结

  • 内部标签的生成代码三步:deserialize_any(TaggedContentVisitor) 得到 (标签, Content),用 ContentDeserializer 遮蔽原来的 __deserializer,按标签 match 一个变体重放。
  • TaggedContentVisitor::visit_map 每个键都用 deserialize_any 读,等于标签名的那个键的值直接读成 __Field,其余键值不论在标签前后都读成 Content;输入是数组时第一个元素是标签(1.0.15,#1058)。
  • 只试一个变体,所以用拿走所有权的 ContentDeserializer;重放阶段报的错没有行列号。
  • unit 变体用 InternallyTaggedUnitVisitor,对象里多余的键会被跳过。
  • 序列化不缓冲:TaggedSerializer 等内层值调 serialize_struct / serialize_map 时插标签;整数、字符串、序列、Option 都在运行期报 cannot serialize tagged newtype variant ... containing ...;tuple 变体在编译期就被拒绝。
  • 相邻标签是 deserialize_struct 加一个生成的 visit_map:标签在前时直接读内容,内容在前时缓冲成 Content。标签按 unit 变体写(1.0.181,#2505),但 visit_seq 读标签仍走 deserialize_identifier,bincode、postcard 读不回来。

💬 关联讨论

发起讨论
加载中...