Rust in Everything
基于 serde 1.0.229

外部标签

主线位置:反序列化主线上 enum 的默认路径,没有缓冲。生成的 Deserialize::deserialize 调 deserialize_enum("Shape", VARIANTS, __Visitor),serde_json 看第一个字节,交出 EnumAccess;__Visitor::visit_enum 先用 variant_seed 读出变体标识 __Field,再按变体形状调 VariantAccess 的四个方法之一,内容直接从 JSON 里读。序列化一侧是对应的 serialize_*_variant。

sequenceDiagram
  participant J as serde_json
  participant V as Shape 的 Visitor
  participant A as VariantAccess
  participant F as __Field
  J->>V: visit_enum(A)
  V->>A: variant_seed
  A->>F: deserialize_identifier
  F-->>V: __field3
  V->>A: struct_variant(FIELDS)
  A->>V: visit_map 或 visit_seq
  V-->>J: Shape::Rect
  J->>J: 检查右花括号

EnumAccess / VariantAccess 这两个 trait 的定义、serde_json 两个实现的分工、"Circle" 撞上 UnitVariantAccess 报什么错,在 SeqAccess、MapAccess 与 EnumAccess 里已经讲过;这一页讲 derive 生成的那一半,以及外部标签特有的几件事:变体标识怎么认、多出来的键报什么、rename / alias / #[serde(other)] 落在哪里。生成代码的源码在 de/enum_externally.rs,213 行。实验在 M2 Max、macOS、rustc 1.99.0 上跑,依赖 serde = "=1.0.229"(derive)、serde_json = "=1.0.154"、bincode = "=1.3.3"。

本页的例子(src/bin/ext.rs):

#[derive(Serialize, Deserialize, Debug)]
enum Shape {
  Dot,
  Circle(f64),
  Pair(u8, u8),
  #[serde(rename = "rect", alias = "box")]
  Rect { w: u32, h: u32 },
}

序列化:变体名作键

生成的 Serialize 对四种变体各调一个方法,带上类型名、变体下标(0u32 到 3u32)和变体名:

                    _serde::Serializer::serialize_unit_variant(
                        __serializer,
                        "Shape",
                        0u32,
                        "Dot",
                    )
                    // ...
                    _serde::Serializer::serialize_newtype_variant(
                        __serializer,
                        "Shape",
                        1u32,
                        "Circle",
                        __field0,
                    )
                    // ...
                    let mut __serde_state = _serde::Serializer::serialize_struct_variant(
                        __serializer,
                        "Shape",
                        3u32,
                        "rect",
                        0 + 1 + 1,
                    )?;

变体名已经是 rename 之后的 "rect";alias 只影响反序列化。外部标签就是 serde 数据模型对 enum 的原生表示:「变体名 + 内容」,不加任何约定,怎么落到字节上完全由格式决定。serde_json 的 serialize_unit_variant 只写名字:

    fn serialize_unit_variant(
        self,
        _name: &'static str,
        _variant_index: u32,
        variant: &'static str,
    ) -> Result<()> {
        self.serialize_str(variant)
    }

serialize_newtype_variant 用 Formatter 的 begin_object、begin_object_key 等钩子写出 {"Circle":,再递归写内容,最后 end_object。tuple 和 struct 变体同理,外面多包一层对象。src/bin/ext.rs ser 用一次往返里打印调用的 LogSer 和 LogWriter 跑前两个变体:

serialize_unit_variant("Shape", 0, "Dot")
write "\""
write "Dot"
write "\""
output: "Dot"
serialize_newtype_variant("Shape", 1, "Circle")
  write "{"
  write "\""
  write "Circle"
  write "\""
  write ":"
  serialize_f64(1.5)
  write "1.5"
  write "}"
output: {"Circle":1.5}

serde_json 不写变体下标;bincode 只写下标(u32),不写名字,后面 #[serde(other)] 一节会用到这一点。四种 serialize_*_variant 在 derive 里怎么生成,在生成的 Serialize。

生成的 Deserialize

enum_externally::deserialize 生成的整体结构只有四块:

    quote_block! {
        #variant_visitor

        #[doc(hidden)]
        struct __Visitor #de_impl_generics #where_clause {
            marker: _serde::#private::PhantomData<#this_type #ty_generics>,
            lifetime: _serde::#private::PhantomData<&#delife ()>,
        }

        #[automatically_derived]
        impl #de_impl_generics _serde::de::Visitor<#delife> for __Visitor #de_ty_generics #where_clause {
            type Value = #this_type #ty_generics;

            fn expecting(&self, __formatter: &mut _serde::#private::Formatter) -> _serde::#private::fmt::Result {
                _serde::#private::Formatter::write_str(__formatter, #expecting)
            }

            fn visit_enum<__A>(self, __data: __A) -> _serde::#private::Result<Self::Value, __A::Error>
            where
                __A: _serde::de::EnumAccess<#delife>,
            {
                #match_variant
            }
        }

        #variants_stmt

        _serde::Deserializer::deserialize_enum(
            __deserializer,
            #type_name,
            VARIANTS,
            __Visitor {
                marker: _serde::#private::PhantomData::<#this_type #ty_generics>,
                lifetime: _serde::#private::PhantomData,
            },
        )
    }

#variant_visitor 是变体标识 __Field 和它的 __FieldVisitor,#variants_stmt 是 const VARIANTS,两者都由 enum_.rs 的 prepare_enum_variant_enum 生成,内部标签和相邻标签也用它。__Visitor 只实现 visit_enum;JSON 里给一个数组或数字,serde_json 在 deserialize_enum 里就先报错了,轮不到 Visitor。#de_impl_generics 这些泛型片段在泛型与 bound。

变体标识 __Field

变体标识和结构体的字段标识是同一个生成器(identifier::deserialize_generated,is_variant = true),visit_str / visit_bytes / visit_u64 三种认法的来历在生成的 Deserialize。Shape 展开后:

            enum __Field {
                __field0,
                __field1,
                __field2,
                __field3,
            }
            // ...
                fn visit_u64<__E>(
                    self,
                    __value: u64,
                ) -> _serde::__private229::Result<Self::Value, __E>
                where
                    __E: _serde::de::Error,
                {
                    match __value {
                        0u64 => _serde::__private229::Ok(__Field::__field0),
                        1u64 => _serde::__private229::Ok(__Field::__field1),
                        2u64 => _serde::__private229::Ok(__Field::__field2),
                        3u64 => _serde::__private229::Ok(__Field::__field3),
                        _ => {
                            _serde::__private229::Err(
                                _serde::de::Error::invalid_value(
                                    _serde::de::Unexpected::Unsigned(__value),
                                    &"variant index 0 <= i < 4",
                                ),
                            )
                        }
                    }
                }
                fn visit_str<__E>(
                    self,
                    __value: &str,
                ) -> _serde::__private229::Result<Self::Value, __E>
                where
                    __E: _serde::de::Error,
                {
                    match __value {
                        "Dot" => _serde::__private229::Ok(__Field::__field0),
                        "Circle" => _serde::__private229::Ok(__Field::__field1),
                        "Pair" => _serde::__private229::Ok(__Field::__field2),
                        "box" => _serde::__private229::Ok(__Field::__field3),
                        "rect" => _serde::__private229::Ok(__Field::__field3),
                        _ => {
                            _serde::__private229::Err(
                                _serde::de::Error::unknown_variant(__value, VARIANTS),
                            )
                        }
                    }
                }
            // ...
            const VARIANTS: &'static [&'static str] = &[
                "Dot",
                "Circle",
                "Pair",
                "box",
                "rect",
            ];

和字段标识比,有三处不同:

  • 没有 __ignore。 结构体遇到不认识的字段默认跳过(Rect 自己的字段标识里就有 _ => Ok(__Field::__ignore));变体标识不认识就是错,deserialize_identifier 的兜底分支在 is_variant 时是 unknown_variant(__value, VARIANTS),字段是 unknown_field(__value, FIELDS)。
  • 下标越界的错误信息。 "variant index 0 <= i < 4",字段是 "field index ..."。JSON 用不到这个分支,bincode 这类按下标写变体的格式会走到。
  • VARIANTS 里是反序列化时接受的全部名字。 每个变体的 aliases() 是一个 BTreeSet<Name>,包括主名字本身,按字符串排序,所以 Rect 贡献了 "box"、"rect" 两项,"Rect" 不在其中。这个数组传给 deserialize_enum,也出现在 unknown variant 的报错里。

visit_enum:一个 match

#match_variant 展开后是一个 match,每个变体一条分支:

                    match _serde::de::EnumAccess::variant(__data) {
                        _serde::__private229::Ok((__Field::__field0, __variant)) => {
                            _serde::de::VariantAccess::unit_variant(__variant)?;
                            _serde::__private229::Ok(Shape::Dot)
                        }
                        _serde::__private229::Ok((__Field::__field1, __variant)) => {
                            _serde::__private229::Result::map(
                                _serde::de::VariantAccess::newtype_variant::<
                                    f64,
                                >(__variant),
                                Shape::Circle,
                            )
                        }
                        _serde::__private229::Ok((__Field::__field2, __variant)) => {
                            // ... 一个局部的 __Visitor,只实现 visit_seq
                            _serde::de::VariantAccess::tuple_variant(
                                __variant,
                                2usize,
                                __Visitor { /* ... */ },
                            )
                        }
                        _serde::__private229::Ok((__Field::__field3, __variant)) => {
                            // ... 局部的 __Field(w、h、__ignore)和 __Visitor(visit_seq、visit_map)
                            const FIELDS: &'static [&'static str] = &["w", "h"];
                            _serde::de::VariantAccess::struct_variant(
                                __variant,
                                FIELDS,
                                __Visitor { /* ... */ },
                            )
                        }
                        _serde::__private229::Err(__err) => {
                            _serde::__private229::Err(__err)
                        }
                    }

(花括号里的 /* ... */ 是本文删掉的 marker / lifetime 两个字段。)每条分支由 deserialize_externally_tagged_variant 按变体形状生成:

变体调用内容怎么读
Dotunit_variant(__variant)?格式确认没有内容(或内容是 unit)
Circle(f64)newtype_variant::<f64>(__variant)直接 f64::deserialize
Pair(u8, u8)tuple_variant(__variant, 2, __Visitor)局部 __Visitor::visit_seq,和 tuple struct 的生成器相同(TupleForm::ExternallyTagged)
Rect { w, h }struct_variant(__variant, FIELDS, __Visitor)局部 __Visitor 的 visit_seq / visit_map,和 struct 的生成器相同(StructForm::ExternallyTagged)

tuple 和 struct 变体的内容就是一个匿名的 tuple struct / struct,所以直接复用 tuple::deserialize 和 struct_::deserialize,只是外层的入口从 deserialize_tuple_struct / deserialize_struct 换成了 VariantAccess 的方法。newtype_variant 加了 skip_deserializing 的字段时改调 unit_variant 再填默认值;变体或字段写了 deserialize_with 时套一个 __DeserializeWith 包装类型。最后那条 Err(__err) => Err(__err) 是 1.0.229 把 variant(__data)? 改掉的结果,见怎么读这一卷。

enum 一个变体都没有(或全部 skip_deserializing)时,match_variant 换成 Result::map(EnumAccess::variant::<__Field>(__data), |(__impossible, _)| match __impossible {}):变体标识照样读,读成功了也不可能有值,所以对任何输入都报错。

serde_json 一侧:一个键,一个值

serde_json 的 deserialize_enum:

        match tri!(self.parse_whitespace()) {
            Some(b'{') => {
                check_recursion! {
                    self.eat_char();
                    let ret = visitor.visit_enum(VariantAccess::new(self));
                }
                let value = tri!(ret);

                match tri!(self.parse_whitespace()) {
                    Some(b'}') => {
                        self.eat_char();
                        Ok(value)
                    }
                    Some(_) => Err(self.error(ErrorCode::ExpectedSomeValue)),
                    None => Err(self.error(ErrorCode::EofWhileParsingObject)),
                }
            }
            Some(b'"') => visitor.visit_enum(UnitVariantAccess::new(self)),
            Some(_) => Err(self.peek_error(ErrorCode::ExpectedSomeValue)),
            None => Err(self.peek_error(ErrorCode::EofWhileParsingValue)),
        }

{ 开头时,VariantAccess 的 variant_seed 读一个键、吃掉冒号,四个方法读冒号后面的那一个值;visit_enum 返回后,这里要求紧接着就是 }。" 开头时交出 UnitVariantAccess,只有 unit_variant 能成功。别的首字节一律 expected value。所以「外部标签」在 serde_json 里的确切含义是:一个字符串,或者恰好只有一个键的对象。src/bin/ext.rs ok 和 err:

// "ok"
json::<Shape>(r#""Dot""#);
json::<Shape>(r#"{"Circle":1.5}"#);
json::<Shape>(r#"{"Pair":[1,2]}"#);
json::<Shape>(r#"{"rect":{"w":3,"h":4}}"#);
json::<Shape>(r#"{"box":{"w":3,"h":4}}"#);
json::<Shape>(r#"{"rect":[3,4]}"#);
// "err"
json::<Shape>(r#"{"Rect":{"w":3,"h":4}}"#);
json::<Shape>(r#""Hexagon""#);
json::<Shape>(r#"{"Dot":null,"Circle":1}"#);
json::<Shape>(r#"{}"#);
json::<Shape>(r#"["Dot"]"#);
json::<Shape>(r#"0"#);
json::<Shape>(r#"{"Pair":[1,2,3]}"#);
"Dot"                        => Ok(Dot)
{"Circle":1.5}               => Ok(Circle(1.5))
{"Pair":[1,2]}               => Ok(Pair(1, 2))
{"rect":{"w":3,"h":4}}       => Ok(Rect { w: 3, h: 4 })
{"box":{"w":3,"h":4}}        => Ok(Rect { w: 3, h: 4 })
{"rect":[3,4]}               => Ok(Rect { w: 3, h: 4 })
{"Rect":{"w":3,"h":4}}       => Err(unknown variant `Rect`, expected one of `Dot`, `Circle`, `Pair`, `box`, `rect` at line 1 column 7)
"Hexagon"                    => Err(unknown variant `Hexagon`, expected one of `Dot`, `Circle`, `Pair`, `box`, `rect` at line 1 column 9)
{"Dot":null,"Circle":1}      => Err(expected value at line 1 column 11)
{}                           => Err(expected value at line 1 column 2)
["Dot"]                      => Err(expected value at line 1 column 1)
0                            => Err(expected value at line 1 column 1)
{"Pair":[1,2,3]}             => Err(trailing characters at line 1 column 14)
  • rename 之后原名 Rect 就不认了,alias 加的 box 认。
  • {"rect":[3,4]} 也能读:struct_variant 在 serde_json 里是 deserialize_struct(self.de, "", fields, visitor),而 serde_json 的 deserialize_struct 同时接受对象和数组。
  • 多一个键:{"Dot":null 读完,visit_enum 已经返回 Ok(Dot),再往后看到的是 , 而不是 },报 expected value,位置是第 11 列的逗号。这句话是 ErrorCode::ExpectedSomeValue 的文字,说的并不是「多了一个键」,排查时要靠列号。空对象 {} 在 variant_seed 里看到 },报的也是它。
  • {"Pair":[1,2,3]} 报 trailing characters:tuple 变体的 visit_seq 取了两个元素就返回,serde_json 的 end_seq 发现 ] 之前还有 ,3。

从 serde_json::Value 读(from_value)走的是另一个 Deserializer。Map 的 deserialize_enum 先数键的个数,不是一个就报错,信息更直白(src/bin/ext.rs value):

from_value {"Dot":null,"Circle":1}  => Err(Error("invalid value: map, expected map with a single key", line: 0, column: 0))
from_value {}                       => Err(Error("invalid value: map, expected map with a single key", line: 0, column: 0))

不缓冲:值直接从解析器来

外部标签的标签在内容之前,读到键就知道是哪个变体,后面的内容可以直接交给对应变体的 Visitor。用打印包装 LogDe 套住 serde_json 的 Deserializer(src/bin/ext_trace.rs,同样的 Shape 去掉了 Pair):

for input in [r#"{"box":{"w":3,"h":4}}"#, r#"{"rect":[3,4]}"#] {
  println!("-- {input}");
  let mut de = serde_json::Deserializer::from_str(input);
  let v = Shape::deserialize(LogDe(&mut de));
  println!("=> {v:?}");
}
-- {"box":{"w":3,"h":4}}
deserialize_enum("Shape", ["Dot", "Circle", "box", "rect"])
  visit_enum
    variant_seed
      deserialize_identifier
        visit_borrowed_str("box")
    struct_variant(["w", "h"])
      visit_map
        next_key_seed
          deserialize_identifier
            visit_borrowed_str("w")
        next_value_seed
          deserialize_u32
            visit_u64(3)
        next_key_seed
          deserialize_identifier
            visit_borrowed_str("h")
        next_value_seed
          deserialize_u32
            visit_u64(4)
        next_key_seed
          => None
=> Ok(Rect { w: 3, h: 4 })
-- {"rect":[3,4]}
deserialize_enum("Shape", ["Dot", "Circle", "box", "rect"])
  visit_enum
    variant_seed
      deserialize_identifier
        visit_borrowed_str("rect")
    struct_variant(["w", "h"])
      visit_seq
        next_element_seed
          deserialize_u32
            visit_u64(3)
        next_element_seed
          deserialize_u32
            visit_u64(4)
=> Ok(Rect { w: 3, h: 4 })

和本部分索引页里另外三种表示的调用序列对照,要看的是叶子:w 和 h 都是 deserialize_u32 → visit_u64,从 serde_json 的解析器直接读出来,没有出现 deserialize_any,也没有中间的 Content。数组形式 [3,4] 走 visit_seq,同样逐个直接读。变体标识用的是 deserialize_identifier,在 serde_json 里就是 deserialize_str,于是 visit_borrowed_str("box") 直接借用输入里的字符串,不分配。

不需要 deserialize_any,也就不挑格式:格式只要能按 deserialize_enum 的提示读出「哪个变体」,再按 VariantAccess 的提示读内容。bincode 的 variant_seed 读一个 u32 下标交给变体标识(走 visit_u64),见 SeqAccess、MapAccess 与 EnumAccess。src/bin/bincode_reprs.rs 把索引页那四个只差属性的 enum 各用 bincode 1.3.3 往返一次(外层 Ok 是序列化,内层是反序列化):

External  Dot -> Ok(Ok(Dot))
External  At(Point { x: 1, y: 2 }) -> Ok(Ok(At(Point { x: 1, y: 2 })))
External  Rect { w: 3, h: 4 } -> Ok(Ok(Rect { w: 3, h: 4 }))
Internal  Dot -> Ok(Err(DeserializeAnyNotSupported))
Internal  At(Point { x: 1, y: 2 }) -> Ok(Err(DeserializeAnyNotSupported))
Internal  Rect { w: 3, h: 4 } -> Ok(Err(DeserializeAnyNotSupported))
Adjacent  Dot -> Ok(Err(Custom("Bincode does not support Deserializer::deserialize_identifier")))
Adjacent  At(Point { x: 1, y: 2 }) -> Ok(Err(Custom("Bincode does not support Deserializer::deserialize_identifier")))
Adjacent  Rect { w: 3, h: 4 } -> Ok(Err(Custom("Bincode does not support Deserializer::deserialize_identifier")))
Untagged  Dot -> Ok(Err(DeserializeAnyNotSupported))
Untagged  At(Point { x: 1, y: 2 }) -> Ok(Err(DeserializeAnyNotSupported))
Untagged  Rect { w: 3, h: 4 } -> Ok(Err(DeserializeAnyNotSupported))

只有外部标签三种变体都回来了。内部标签和 untagged 卡在 deserialize_any(DeserializeAnyNotSupported);相邻标签的入口是 deserialize_struct,bincode 把它当序列交给生成的 visit_seq,而 visit_seq 读第一个元素(标签)用的是 next_element::<__Field>(),__Field::deserialize 调 deserialize_identifier,bincode 1.3.3 不支持。

#[serde(other)]:兜底的 unit 变体

#[serde(other)] 标在一个 unit 变体上,让不认识的变体名都落到它。prepare_enum_variant_enum 把它变成变体标识的兜底分支:

    let fallthrough = deserialized_variants
        .clone()
        .find(|(_i, variant)| variant.attrs.other())
        .map(|(i, _variant)| {
            let ignore_variant = field_i(i);
            quote!(_serde::#private::Ok(__Field::#ignore_variant))
        });

有了 fallthrough,visit_str / visit_bytes 的 _ => 分支就不再是 unknown_variant,而是 Ok(__Field::__field2)。check_identifier 在编译期限制它:只能有一个、必须在最后一个变体上、必须是 unit 变体、不能用在 untagged enum 上(报 #[serde(other)] cannot appear on untagged enum)。这个属性是 1.0.79 加的(PR #1382)。

src/bin/ext_other.rs other:

#[derive(Serialize, Deserialize, Debug)]
enum Event {
  Click { x: i32 },
  Key(char),
  #[serde(other)]
  Unknown,
}

json::<Event>(r#"{"Click":{"x":1}}"#);
json::<Event>(r#""Scroll""#);
json::<Event>(r#"{"Scroll":null}"#);
{"Click":{"x":1}}      => Ok(Click { x: 1 })
"Scroll"               => Ok(Unknown)
{"Scroll":null}        => Ok(Unknown)

字符串形式的 "Scroll" 和带 null 的 {"Scroll":null} 都落到 Unknown。注意兜底只换掉了「变体名认不出」这一步:变体标识成了 Unknown,visit_enum 接着照 Unknown 的形状调 unit_variant,内容那一步仍由格式检查。

❗IMPORTANT
动脑筋:带内容的未知变体

还是上面的 Event,输入换成 {"Scroll":{"dy":3}}:Scroll 不认识,落到 Unknown。serde_json::from_str::<Event> 返回 Ok(Unknown) 还是错误?如果是错误,报在第几列?

#[serde(other)] 也接住未知的数字下标。bincode 写变体只写下标,src/bin/ext_other.rs index 构造一个下标为 9 的输入:

let key = bincode::serialize(&Event::Key('k')).unwrap();
println!("Event::Key as bincode: {key:?}");
let unknown = [9u8, 0, 0, 0];
let e: Result<Event, _> = bincode::deserialize(&unknown);
println!("{unknown:?} as Event: {e:?}");
Event::Key as bincode: [1, 0, 0, 0, 107]
[9, 0, 0, 0] as Event: Ok(Unknown)

Event 展开后的 visit_u64 里,越界分支也是兜底:

                    match __value {
                        0u64 => _serde::__private229::Ok(__Field::__field0),
                        1u64 => _serde::__private229::Ok(__Field::__field1),
                        2u64 => _serde::__private229::Ok(__Field::__field2),
                        _ => _serde::__private229::Ok(__Field::__field2),
                    }

源码里是 u64_fallthrough_arm:有 fallthrough 就用它,没有才生成 "variant index 0 <= i < N" 的 invalid_value。

📝NOTE
冷知识:数字下标的兜底是 1.0.122 才对齐的

1.0.122 之前,#[serde(other)] 的兜底只接在 visit_str / visit_bytes 上,visit_u64 的越界分支总是报 variant index 0 <= i < N。PR #1914(「Consistently handle numeric and string/bytes keys of field names」)的提交 07374746 把它改成上面的 u64_fallthrough_arm,提交说明是「Allow field identifiers be any numbers if #[serde(other)] is used. Thus behavior synchronized between string/bytes identifiers and numeric identifiers」。PR 的动机是字段标识(给内部标签用的 field_identifier enum),但改的是字段和变体共用的 deserialize_identifier,所以变体标识的 other 也一起受益:bincode 里出现未知的变体下标时落到 Unknown,而不是报错。首次发布是 1.0.122,发布说明写的是「Handle unrecognized numeric field keys during deserialization of a field_identifier, equivalently to string field keys (#1914)」。

下标还牵出一个现存的不对称。生成的 Serialize 用变体在源码里的位置做下标,而变体标识的 visit_u64 只给参与反序列化的变体编号(deserialized_variants 先过滤掉了 skip_deserializing 的)。中间有一个变体被跳过,后面的下标就错开了。src/bin/ext_other.rs gap:

#[derive(Serialize, Deserialize, Debug)]
enum Gap {
  A,
  #[serde(skip_deserializing)]
  B,
  C,
}
Gap::C as bincode: [2, 0, 0, 0]
bincode back: Err(Custom("invalid value: integer `2`, expected variant index 0 <= i < 2"))

Gap::C 序列化成下标 2(展开里是 serialize_unit_variant(__serializer, "Gap", 2u32, "C")),反序列化一侧的 visit_u64 却是 0u64 => __field0、1u64 => __field2,2 越界。JSON 按名字认变体,不受影响;只按下标认的格式会读错或读不回来。这是一个开着的问题:issue #2614(「Binary serialize/deserialize inconsistency of enums with skipped fields」),里面的例子用 #[serde(skip)],bincode 和 postcard 下 C 被读成了 D。

variant_identifier:只要变体标识

#[serde(variant_identifier)] 让一个只有 unit 变体的 enum 的 Deserialize 只生成「认变体名」那一半:不调 deserialize_enum,而是 deserialize_custom 生成的 deserialize_identifier(__deserializer, __FieldVisitor),值就是 enum 自己。它和 field_identifier 是一对,用途是手写 visit_enum 时当 variant_seed 的类型。serde 自己的 OsString 就是这么写的:serde_core 里不能用 derive,于是用一个宏 variant_identifier! 手工生成同样的东西,注释写着「If this were outside of the serde crate, it would just use: #[derive(Deserialize)] #[serde(variant_identifier)]」:

variant_identifier! {
    OsStringKind (Unix; b"Unix"; 0, Windows; b"Windows"; 1)
    "`Unix` or `Windows`",
    OSSTR_VARIANTS
}

OsStringVisitor 的 visit_enum 里 match tri!(data.variant()) { (OsStringKind::Unix, v) => v.newtype_variant().map(OsString::from_vec), ... },读出变体后自己决定怎么读内容,这正是 derive 的 visit_enum 做的事。

单独拿来反序列化时,它就是一个标识符(src/bin/ext_other.rs ident):

#[derive(Deserialize, Debug)]
#[serde(variant_identifier)]
enum Kind {
  Circle,
  Square,
}
"Square"               => Ok(Square)
1                      => Err(invalid type: integer `1`, expected variant identifier at line 1 column 1)
{"Square":null}        => Err(invalid type: map, expected variant identifier at line 1 column 0)
"Hexagon"              => Err(unknown variant `Hexagon`, expected `Circle` or `Square` at line 1 column 9)

"Square" 能读,{"Square":null} 不能:这不是外部标签的 enum,没有 deserialize_enum 那一层。1 也不行:serde_json 的 deserialize_identifier 是 deserialize_str,数字直接 invalid type;下标那条路只有 bincode 这类格式的 variant_seed 会走。只有两个名字时,unknown_variant 把列表写成 expected `Circle` or `Square`。check.rs 里还规定 variant_identifier 的变体只能是 unit,也不能带 #[serde(other)](#[serde(other)] may not be used on a variant identifier)。

动脑筋答案

src/bin/ext_other.rs quiz:

{"Scroll":{"dy":3}}    => Err(invalid type: map, expected unit at line 1 column 10)

是错误,报在第 10 列。Scroll 经兜底变成 __Field::__field2(Unknown),visit_enum 按 Unknown 是 unit 变体调 unit_variant。serde_json 的 VariantAccess::unit_variant 是 de::Deserialize::deserialize(self.de),也就是从冒号后面读一个 ():读到 null 才行,读到 { 就是 invalid type: map, expected unit。第 10 列正是 {"Scroll": 之后那个 {。#[serde(other)] 只能接住「名字不认识、内容为空」的变体;要连内容一起吞掉,可以把兜底变体写成 #[serde(untagged)] Other(serde_json::Value)(变体级的 untagged,1.0.164 起,PR #2403)。src/bin/untagged_other.rs 对同一个输入的结果:

Ok(Other(Object {"Scroll": Object {"dy": Number(3)}}))

代价是整个 enum 改走缓冲:生成代码先把整个值读进 Content,在它上面按外部标签试一遍,失败了再交给 untagged 变体,见 untagged。

小结

  • 外部标签是数据模型对 enum 的原生表示。序列化调 serialize_*_variant,serde_json 把 unit 变体写成 "Dot",其余写成 {"变体名":内容}。
  • 生成的 Deserialize 调 deserialize_enum;visit_enum 是一个 match:EnumAccess::variant 取出 __Field,每个变体按形状调 unit_variant、newtype_variant、tuple_variant 或 struct_variant,tuple / struct 变体复用 tuple struct / struct 的生成器。
  • 变体标识没有 __ignore,不认识就 unknown_variant;VARIANTS 是全部可接受的名字(含 alias,按字符串排序),rename 之后原名不再被接受。
  • serde_json 只接受字符串或恰好一个键的对象;多一个键报的是 expected value,from_value 一侧报 map with a single key。
  • 内容直接从格式读,没有 deserialize_any、没有 Content,所以 bincode 这类非自描述格式也能用;四种表示里只有它能在 bincode 1.3.3 上往返。
  • #[serde(other)] 只兜住变体名(字符串和 1.0.122 起的数字下标),内容仍按 unit 检查;skip_deserializing 的变体会让按下标认变体的格式错位(issue #2614)。

💬 关联讨论

发起讨论
加载中...