Rust in Everything
基于 serde 1.0.229

Bincode does not support the serde::Deserializer::deserialize_any method

主线位置:两条主线和缓冲主线在非自描述格式上的交点。bincode、postcard 的字节里没有类型标记,反序列化只能照类型给的提示一步步读;缓冲主线要的 deserialize_any 它们给不了。序列化一侧,它们要求事先知道长度,也完全依赖「写了什么、就按同样的顺序读什么」。

flowchart TB
  ty["一个 serde 类型"] --> q1{"要 deserialize_any 吗"}
  q1 -->|"内部标签 untagged Value"| e1["读不回"]
  q1 -->|否| q2{"长度事先知道吗"}
  q2 -->|"flatten collect_seq"| e2["写不出"]
  q2 -->|是| q3{"写读字段一致吗"}
  q3 -->|skip_serializing_if| e3["读错位"]
  q3 -->|"skip_deserializing 变体"| e4["序号错一位"]
  q3 -->|是| ok["往返成功"]

为什么 bincode 答不了「下一个值是什么」,在 serde 解决什么问题 的「自描述格式与非自描述格式」;各种表示怎么用到 deserialize_any,在 enum 的表示与缓冲 这一部分;len 的约定在 Serialize 与 Serializer。这一页把这些散在各处的结论放进一张表,每一格都是实测。

本页实验在 M2 Max、macOS、rustc 1.99.0 上运行,依赖 serde = "=1.0.229"(derive)、serde_json = "=1.0.154"、bincode = "=1.3.3"、postcard = "1"(解析到 1.1.3,开 alloc)。bincode 用 bincode::serialize / bincode::deserialize 的默认配置,postcard 用 to_allocvec / from_bytes。

现象

src/bin/binary.rs:每个类型构造一个值,分别用 bincode、postcard、serde_json 写出再读回,比较读回的值和原值。

#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(tag = "t")]
enum Internal {
  Rec { id: u32 },
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct SkipIf {
  #[serde(skip_serializing_if = "Option::is_none")]
  note: Option<String>,
  id: u32,
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
enum Gap {
  A,
  #[serde(skip_deserializing)]
  B,
  C,
}

// Serialize only the even numbers: the iterator does not know its length.
#[derive(Deserialize, Debug, PartialEq)]
struct Evens(Vec<u32>);

impl Serialize for Evens {
  fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
    s.collect_seq(self.0.iter().filter(|n| *n % 2 == 0))
  }
}

// ... Plain、External、Adjacent、Untagged、Flat、Skip、GapLast、WithValue 同理

fn cell<E: std::fmt::Display, T: Debug + PartialEq>(orig: &T, ser: Result<Vec<u8>, E>, de: impl Fn(&[u8]) -> Result<T, String>) -> String {
  match ser {
    Err(e) => format!("ser Err({e})"),
    Ok(bytes) => match de(&bytes) {
      Ok(back) if &back == orig => format!("{bytes:?} -> same"),
      Ok(back) => format!("{bytes:?} -> DIFFERENT {back:?}"),
      Err(e) => format!("{bytes:?} -> de Err({e})"),
    },
  }
}

fn row<T: Serialize + DeserializeOwned + Debug + PartialEq>(label: &str, v: T) {
  println!("{label}: {v:?}");
  let b = cell(&v, bincode::serialize(&v), |b| bincode::deserialize::<T>(b).map_err(|e| e.to_string()));
  println!("  bincode  {b}");
  let p = cell(&v, postcard::to_allocvec(&v), |b| postcard::from_bytes::<T>(b).map_err(|e| e.to_string()));
  println!("  postcard {p}");
  // ... serde_json 同样往返,打印 JSON 文本
}

输出(跑 3 次相同):

plain struct: Plain { id: 7, name: "a" }
  bincode  [7, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 97] -> same
  postcard [7, 1, 97] -> same
  json     {"id":7,"name":"a"} -> same
external unit: Unit
  bincode  [0, 0, 0, 0] -> same
  postcard [0] -> same
  json     "Unit" -> same
external newtype: New(7)
  bincode  [1, 0, 0, 0, 7, 0, 0, 0] -> same
  postcard [1, 7] -> same
  json     {"New":7} -> same
external struct: Rec { id: 7 }
  bincode  [2, 0, 0, 0, 7, 0, 0, 0] -> same
  postcard [2, 7] -> same
  json     {"Rec":{"id":7}} -> same
internal: Rec { id: 7 }
  bincode  [3, 0, 0, 0, 0, 0, 0, 0, 82, 101, 99, 7, 0, 0, 0] -> de Err(Bincode does not support the serde::Deserializer::deserialize_any method)
  postcard [3, 82, 101, 99, 7] -> de Err(This is a feature that PostCard will never implement)
  json     {"t":"Rec","id":7} -> same
adjacent: New(7)
  bincode  [0, 0, 0, 0, 7, 0, 0, 0] -> de Err(Bincode does not support Deserializer::deserialize_identifier)
  postcard [0, 7] -> de Err(This is a feature that PostCard will never implement)
  json     {"t":"New","c":7} -> same
untagged: New(7)
  bincode  [7, 0, 0, 0] -> de Err(Bincode does not support the serde::Deserializer::deserialize_any method)
  postcard [7] -> de Err(This is a feature that PostCard will never implement)
  json     7 -> same
flatten: Flat { id: 7, inner: Inner { name: "a" } }
  bincode  ser Err(Bincode can only encode sequences and maps that have a knowable size ahead of time)
  postcard ser Err(The length of a sequence must be known)
  json     {"id":7,"name":"a"} -> same
skip_serializing_if, None: SkipIf { note: None, id: 7 }
  bincode  [7, 0, 0, 0] -> de Err(tag for enum is not valid, found 7)
  postcard [7] -> de Err(Found an Option discriminant that wasn't 0 or 1)
  json     {"id":7} -> same
skip_serializing_if, None, id 1: SkipIf { note: None, id: 1 }
  bincode  [1, 0, 0, 0] -> de Err(io error: unexpected end of file)
  postcard [1] -> de Err(Hit the end of buffer, expected more data)
  json     {"id":1} -> same
skip_serializing_if, Some: SkipIf { note: Some("a"), id: 7 }
  bincode  [1, 1, 0, 0, 0, 0, 0, 0, 0, 97, 7, 0, 0, 0] -> same
  postcard [1, 1, 97, 7] -> same
  json     {"note":"a","id":7} -> same
skip: Skip { cache: None, id: 7 }
  bincode  [7, 0, 0, 0] -> same
  postcard [7] -> same
  json     {"id":7} -> same
skip_deserializing variant, A: A
  bincode  [0, 0, 0, 0] -> same
  postcard [0] -> same
  json     "A" -> same
skip_deserializing variant, B: B
  bincode  [1, 0, 0, 0] -> DIFFERENT C
  postcard [1] -> DIFFERENT C
  json     "B" -> de Err(unknown variant `B`, expected `A` or `C` at line 1 column 3)
skip_deserializing variant, C: C
  bincode  [2, 0, 0, 0] -> de Err(invalid value: integer `2`, expected variant index 0 <= i < 2)
  postcard [2] -> de Err(Serde Deserialization Error)
  json     "C" -> same
skipped variant last, C: C
  bincode  [1, 0, 0, 0] -> same
  postcard [1] -> same
  json     "C" -> same
skipped variant last, B: B
  bincode  [2, 0, 0, 0] -> de Err(invalid value: integer `2`, expected variant index 0 <= i < 2)
  postcard [2] -> de Err(Serde Deserialization Error)
  json     "B" -> de Err(unknown variant `B`, expected `A` or `C` at line 1 column 3)
collect_seq(filter): Evens([2, 4])
  bincode  ser Err(Bincode can only encode sequences and maps that have a knowable size ahead of time)
  postcard ser Err(The length of a sequence must be known)
  json     [2,4] -> same
serde_json::Value: WithValue { v: Number(7) }
  bincode  [7, 0, 0, 0, 0, 0, 0, 0] -> de Err(Bincode does not support the serde::Deserializer::deserialize_any method)
  postcard [7] -> de Err(This is a feature that PostCard will never implement)
  json     {"v":7} -> same

一张表

「往返」指写出成功、读回的值和原值相等。

类型 / 属性bincode 1.3.3postcard 1.1.3卡在哪
普通 struct往返往返—
外部标签 enum(unit / newtype / struct 变体)往返往返—
内部标签 tag = "t"能写,读:deserialize_any 不支持能写,读:WontImplementTaggedContentVisitor 要 deserialize_any
相邻标签 tag, content能写,读:deserialize_identifier 不支持能写,读:WontImplement生成的 visit_seq 用 __Field 读标签
untagged能写,读:deserialize_any 不支持能写,读:WontImplement先缓冲成 Content
字段 serde_json::Value能写,读:deserialize_any 不支持能写,读:WontImplementValue 只能 deserialize_any
flatten写:要求事先知道长度写:The length of a sequence must be knownderive 调 serialize_map(None)
collect_seq 一个 filter 迭代器写:要求事先知道长度写:同上serialize_seq(None)
skip_serializing_if,值被跳过能写,读:错位(tag for enum is not valid, found 7 / EOF)能写,读:错位(Option discriminant / EOF)写少了一个字段,读按全部字段
skip_serializing_if,值没跳过往返往返—
#[serde(skip)] 字段往返往返写读两边都跳过
skip_deserializing 变体之前的变体往返往返—
skip_deserializing 变体本身读成下一个变体,不报错读成下一个变体,不报错读的一侧序号重排
skip_deserializing 变体之后的 C(最后一个)能写,读:variant index 0 <= i < 2 越界能写,读:Serde Deserialization Error同上
skip_deserializing 变体放在最后其他变体往返;它自己读:越界报错同左,报错没有原文前面的序号不变

JSON 那一列除了 skip_deserializing 的变体(unknown variant \B``,这是该有的)全部往返成功。下面按「卡在哪」分四类说。

出处

要 deserialize_any 的:内部标签、untagged、Value

bincode 1.3.3 的 deserialize_any 只有一行 Err(Box::new(ErrorKind::DeserializeAnyNotSupported)),这个 ErrorKind 的显示文本就是本页标题;postcard 1.1.3 的 deserialize_any、deserialize_identifier、deserialize_ignored_any 都返回 Error::WontImplement,显示为「This is a feature that PostCard will never implement」。内部标签和 untagged 都要先把整段输入读成 Content,serde_json::Value 本身就是「读到什么算什么」,三者都只能问格式「这里是什么」,见 Content。

写出没有问题:序列化一侧是类型推数据给格式,内部标签就是 serialize_struct 多写一个字段。bincode 的 [3, 0, 0, 0, 0, 0, 0, 0, 82, 101, 99, 7, 0, 0, 0] 是长度 3 的字符串 "Rec" 加 u32 的 7;读的一方不知道开头 8 个字节是一个长度,这些字节也没告诉它。

相邻标签不走 deserialize_any,卡在另一处:bincode 把 struct 当元组读,生成的 visit_seq 读第一个元素(标签)要经 __Field 的 deserialize_identifier,bincode 不支持(「Bincode does not support Deserializer::deserialize_identifier」),postcard 同样返回 WontImplement。细节在 内部标签与相邻标签 的「相邻标签与 bincode」。

长度未知的:flatten、filter 迭代器

二进制格式没有结束符,序列的长度要写在元素前面,bincode 1.3.3 的 serialize_seq 第一行就是 len.ok_or(ErrorKind::SequenceMustHaveLength)?。有 flatten 字段的 struct,derive 改调 serialize_map(None);collect_seq 只在迭代器的 size_hint 上下界相等时才传 Some(n),filter 的下界是 0。两者在第一个字节写出之前就失败。机制见 Serialize 与 Serializer 的「谁传 None」和 flatten。

写读不一致的:skip_serializing_if

SkipIf { note: None, id: 7 } 在 bincode 里写成 [7, 0, 0, 0]:note 被跳过,只剩 id。读的时候 bincode 的 deserialize_struct 是 self.deserialize_tuple(fields.len(), visitor),按 FIELDS 的个数读元组;derive 的 deserialize_seq 生成的 visit_seq 按声明顺序一个个 next_element,第一个要的是 Option<String>。于是 7 被当成 Option 的标签字节:bincode 报「tag for enum is not valid, found 7」,postcard 报「Found an Option discriminant that wasn't 0 or 1」。id 换成 1,标签就成了 Some,接着把后面的字节当字符串长度读,读到末尾报 EOF。哪种报错都和真正的原因(少写了一个字段)对不上。

JSON 没事,是因为 JSON 写了键名:读的一方按键认字段,缺了的 Option 字段由 missing_field 补成 None(见 missing field)。#[serde(skip)] 也没事:它让字段在写、读两边都不出现,FIELDS 里没有它,位置对得上。

📝NOTE
没有蠢问题

问: derive 给 serialize_struct 传的 len 是运行时算的,跳过的字段不计入(Serialize 与 Serializer)。bincode 拿到了 len = 1,为什么不把它写进去,读的时候就知道少了一个?

答: 因为 bincode 1.3.3 的 serialize_struct(self, _name, _len) 直接丢掉了 _len,struct 不写任何长度前缀:每个字段都写、读两边固定,是它体积小的原因之一。就算写了个数,读的一方也只知道「少了一个」,不知道少的是哪一个:bincode 不写键名,按位置认字段。要知道是哪一个,得在每个字段前写下标或名字,那就是另一种格式了。

serde 其实给格式留了一个钩子。derive 在跳过字段时会调 SerializeStruct::skip_field,默认实现什么也不做:

    #[inline]
    fn skip_field(&mut self, key: &'static str) -> Result<(), Self::Error> {
        let _ = key;
        Ok(())
    }

bincode 1.3.3 和 postcard 1.1.3 的源码里都没有出现 skip_field,用的是这个空实现。

📝NOTE
冷知识:skip_field 是为一种「按整数键写字段」的格式加的

issue #960「Pass field index to SerializeStruct::serialize_field」提出:「This would enable CBOR to deserialize correctly from a packed representation (integer keys) with fields that are conditionally skipped」。CBOR 的紧凑表示用字段下标代替键名,可是 serialize_field 只给名字,格式要自己数第几个字段;有字段被 skip_serializing_if 跳过时,格式根本不知道,数出来的下标就错了。

PR #1022「Inform serializers about skipped fields.」没有改 serialize_field 的签名(那会破坏所有已有的格式),而是加了一个带默认空实现的 skip_field,derive 在跳过时调用它,格式可以借此把计数加一。它首次发布在 1.0.13,发布说明写的是「Add an optional skip_field method so serializers can be notified when a field is skipped (#1022)」。

现在这个钩子还在,但 bincode 1.3.3、postcard 1.1.3 都没有用它:它们按位置写字段,skip_field 能告诉它们「这里少了一个」,却没有地方把这件事写进字节里。

序号重排的:skip_deserializing 变体

Gap { A, #[serde(skip_deserializing)] B, C } 写出时 B 的序号是 1、C 是 2,展开里是 serialize_unit_variant(__serializer, "Gap", 2u32, "C")(外部标签 里有完整的展开)。读的一侧先滤掉 skip_deserializing 的变体,再给剩下的重新编号:u64_mapping 是 deserialized_fields.iter().enumerate(),A 是 0,C 成了 1。

        let u64_mapping = deserialized_fields.iter().enumerate().map(|(i, field)| {
            let i = i as u64;
            let ident = &field.ident;
            quote!(#i => _serde::#private::Ok(#this_value::#ident))
        });

于是:

  • B 写出的 1 读成了 C,不报错。 这是表里唯一一个「读成功但读错」的格子。本该被拒绝的变体,静静地变成了另一个。
  • C 写出的 2 越界。 它是最后一个变体,没有「下一个」可读;issue #2614 里的例子是 A、#[serde(skip)] B、C、D(skip 包含 skip_deserializing),C 在 bincode 和 postcard 上都读成了 D。bincode 报 invalid value: integer \2`, expected variant index 0 <= i < 2;postcard 的 de::Error::custom` 丢掉了消息,只剩「Serde Deserialization Error」。
  • 把被跳过的变体放到最后,前面的序号就不受影响(GapLast),只有它自己越界报错。

这是开着的 issue #2614「Binary serialize/deserialize inconsistency of enums with skipped fields」,写作时 1.0.229 仍是这个行为。JSON 按名字认变体,不受影响。

修法

  • 给二进制格式用外部标签。 四种表示里只有它在这两个格式上能往返。同一个类型既要发 JSON(想要内部标签)又要存 bincode 时,分成两个类型,用 From 互转,比在一个类型上兼顾两种格式可靠。
  • 不用 flatten,不 collect_seq 一个长度未知的迭代器。 前者把字段直接写在 struct 里;后者先 collect 成 Vec,或者用 ExactSizeIterator。
  • skip_serializing_if 只留给 JSON 一类按键名读的格式。 给二进制格式用的类型去掉它;字段确实不需要传输,用 #[serde(skip)],写读两边一致。
  • skip_deserializing 的变体放到 enum 的最后,或者干脆不用。不能挪的,至少别让它出现在要读回来的数据里。
  • 字段别用 serde_json::Value,换成具体的类型或外部标签的 enum。

怎么确认没中招

对每个会写进二进制格式的类型,每个变体、每个 Option 字段的 None 和 Some,都做一次往返断言:

fn roundtrip<T: Serialize + DeserializeOwned + PartialEq + std::fmt::Debug>(v: &T) {
  let bytes = bincode::serialize(v).unwrap();
  assert_eq!(&bincode::deserialize::<T>(&bytes).unwrap(), v);
  let bytes = postcard::to_allocvec(v).unwrap();
  assert_eq!(&postcard::from_bytes::<T>(&bytes).unwrap(), v);
}

一定要比较读回的值,不能只看有没有报错:Gap::B 读成 Gap::C 时 deserialize 返回的是 Ok。也不要拿 JSON 的往返测试代替:上面的表里 JSON 一列几乎全绿,问题只在二进制格式上出现。

小结

  • bincode 1.3.3、postcard 1.1.3 不支持 deserialize_any(postcard 连 deserialize_identifier 也不支持):内部标签、untagged、serde_json::Value 能写不能读,相邻标签同样读不回。外部标签是唯一能往返的 enum 表示。
  • 长度必须事先知道:flatten(serialize_map(None))和 filter 迭代器的 collect_seq 在写出第一步就失败。
  • skip_serializing_if 跳过字段后,按位置读的格式整体错位,报错和原因无关;#[serde(skip)] 两边一致,没事。serde 1.0.13 起有 skip_field 钩子(PR #1022),这两个格式都没用。
  • skip_deserializing 变体让读的一侧重新编号:它自己读成下一个变体且不报错,它后面的变体也跟着错位,最后一个越界报错(issue #2614,未修)。放到最后可以保住其他变体。
  • 防线是对每个变体、每种 Option 状态做二进制往返并比较值。

💬 关联讨论

发起讨论
加载中...