Rust in Everything
基于 serde 1.0.229

u128 is not supported

主线位置:缓冲主线。内部标签、untagged、flatten 先把输入读成 Content 再重放,而 Content 没有 128 位整数;重放时 u128 字段的 deserialize_u128 落到 trait 的默认实现,就是这一句。序列化主线上也有一处缓冲(内部标签或 flatten 里的 struct 变体),同样会撞上默认实现。

flowchart TB
  field["u128 字段"] --> where{"在哪里"}
  where -->|"直接读"| fmt["格式的 deserialize_u128"]
  fmt -->|"json yaml bincode"| ok["Ok"]
  fmt -->|"toml urlencoded"| bad["默认实现报错"]
  where -->|"内部标签 flatten"| ct["Content 重放"]
  ct --> bad
  where -->|untagged| nm["no match 一句话"]

Content 有哪些变体、读入和重放各少了什么,在 Content;这一页只讲碰到之后怎么办。

本页实验在 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)、serde_yaml = "=0.9.34"、serde_urlencoded = "=0.7.1"、toml = "0.8"(0.8.23)、serde_with = "3"(3.24.0)。

现象

src/bin/u128.rs:同一个 Big { n: u128 },直接读、放进内部标签、untagged、flatten,以及 u128 在外层、旁边有个 flatten 字段:

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Big {
  n: u128,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "t")]
enum Internal {
  V(Big),
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum Untagged {
  V(Big),
}

#[derive(Serialize, Deserialize, Debug)]
struct Flat {
  id: u32,
  #[serde(flatten)]
  big: Big,
}

// u128 in the outer struct, next to a flatten field.
#[derive(Serialize, Deserialize, Debug)]
struct OuterBig {
  n: u128,
  #[serde(flatten)]
  rest: std::collections::HashMap<String, u32>,
}

fn main() {
  let max = u128::MAX;
  println!("deserialize, n = 7");
  de::<Big>("plain", r#"{"n":7}"#);
  de::<Internal>("internal", r#"{"t":"V","n":7}"#);
  // ... 相邻标签的两行留给动脑筋
  de::<Untagged>("untagged", r#"{"n":7}"#);
  de::<Flat>("flatten", r#"{"id":1,"n":7}"#);
  de::<OuterBig>("outer+flat", r#"{"n":7,"x":1}"#);
  println!("deserialize, n = u128::MAX");
  de::<Big>("plain", &format!(r#"{{"n":{max}}}"#));
  de::<Internal>("internal", &format!(r#"{{"t":"V","n":{max}}}"#));
}

de 是 serde_json::from_str 加打印。输出(相邻标签两行在页末):

deserialize, n = 7
  plain        Ok(Big { n: 7 })
  internal     Err(u128 is not supported)
  untagged     Err(data did not match any variant of untagged enum Untagged)
  flatten      Err(u128 is not supported at line 1 column 14)
  outer+flat   Ok(OuterBig { n: 7, rest: {"x": 1} })
deserialize, n = u128::MAX
  plain        Ok(Big { n: 340282366920938463463374607431768211455 })
  internal     Err(u128 is not supported)
  • 直接读没有问题,连 u128::MAX 都能读:serde_json 自己实现了 deserialize_u128。
  • 内部标签、flatten 里连 7 都不行。 失败和值的大小无关,只和「有没有经过 Content」有关。
  • untagged 把它藏起来了,只剩一句 no match。真正的原因要按 untagged-no-match 的办法逐个变体去试。
  • u128 在外层就没事:有 flatten 的 struct 只把没认出的键缓冲进 Content(见 flatten),认出的 n 照常从 serde_json 直接读。被 flatten 的那部分里的 u128 才读不回。

出处

数据模型的基本类型里,只有 128 位整数的方法在 trait 里带默认实现,默认就是报错。Deserializer::deserialize_u128:

    fn deserialize_u128<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>,
    {
        let _ = visitor;
        Err(Error::custom("u128 is not supported"))
    }

Serializer::serialize_u128 是同样的一句,i128 的两个方法报 i128 is not supported。这两个类型是 1.0.60 才加进数据模型的,默认实现让已有的格式不用改代码就能编译,代价是不支持的格式到运行期才报错(来历见 serde 解决什么问题)。

Content 的重放用的 ContentDeserializer / ContentRefDeserializer 没有覆盖 deserialize_u128,读入用的 ContentVisitor 也没有 visit_u128,Content 里根本没有放 128 位整数的变体。所以只要值经过缓冲,就是这句报错,见 Content 的「丢掉了什么」。

Error::custom 造的错误不带位置。flatten 那行的 at line 1 column 14 是 serde_json 在外层 deserialize_map 返回前补的,指向整个对象的结尾;内部标签的重放在 serde_json 返回之后,连这个位置都没有。

📝NOTE
没有蠢问题

问: 给 Content 加一个 U128 变体不就好了?为什么一直没加?

答: 有人提过,也有人做过。issue #1682「Untagged enum does not play nice with u128」开于 2019 年,issue #2230「Implement visit_i128 and visit_u128 for ContentVisitor」开于 2022 年,两个都还开着。#2230 的作者写道,他的快速实现「ends up breaking multiple libraries, including serde_json, that use the Unexpected::* enum」。补上 flatten 和内部标签的 PR #2781(「Add support for 128-bit integers in flatten structs and internally tagged enums」)2024 年 7 月提交,到 1.0.229 仍未合并。对照一下:serde_with 3.24.0 自己那份 Content 拷贝里就有 U128(u128) 变体,但那只在 serde_with 的 PickFirst 一类适配器里用,不影响 serde 的 derive。

序列化一侧也有一次缓冲

序列化通常不缓冲,内部标签的 newtype 变体包一个 struct 时,TaggedSerializer 把 serialize_struct 直接转给真正的格式,多写一个标签字段就行。但如果内部包的是一个外部标签 enum 的 struct 变体,或者 flatten 一个这样的 enum:

// Serialization side: an externally tagged struct variant inside an internally tagged newtype.
#[derive(Serialize, Debug)]
enum Shape {
  S { n: u128 },
}

#[derive(Serialize, Debug)]
#[serde(tag = "t")]
enum Wrap {
  V(Shape),
}

#[derive(Serialize, Debug)]
struct FlatShape {
  #[serde(flatten)]
  shape: Shape,
}
serialize, n = 7
  plain        Ok({"n":7})
  internal     Ok({"t":"V","n":7})
  untagged     Ok({"n":7})
  flatten      Ok({"id":1,"n":7})
  tag+struct v Err(u128 is not supported)
  flat enum    Err(u128 is not supported)

这两种要写出 {"t":"V","S":{"n":7}} / {"S":{"n":7}}:在一个已经打开的 map 里写一个键 "S",再写它的值。SerializeMap::serialize_value 要一个完整的值,struct 变体的字段却是一个一个经 serialize_field 来的。TaggedSerializer::serialize_struct_variant 于是返回 SerializeStructVariantAsMapValue,把每个字段先用 ContentSerializer 变成 Content 攒起来,end 时再整体写出(no_std 版本的注释说得直白:「Lack of push-based serialization means we need to buffer the content of the struct variant」)。flatten 走 FlatMapSerializer::serialize_struct_variant,同理。ContentSerializer 也没有覆盖 serialize_u128,于是用了默认实现。

格式自己不支持

不经过缓冲,也要看格式实现了没有。同一个程序的最后一段,Big { n: u128::MAX }(toml 另试了 n = 7):

  let b = Big { n: max };
  println!("  serde_json  {}", r(serde_json::to_string(&b)));
  println!("  json Value  {}", r(serde_json::to_value(&b)));
  println!("  json Value7 {}", r(serde_json::to_value(&Big { n: 7 })));
  let bytes = bincode::serialize(&b).unwrap();
  println!("  bincode     {} back {}", bytes.len(), r(bincode::deserialize::<Big>(&bytes)));
  let bytes = postcard::to_allocvec(&b).unwrap();
  println!("  postcard    {} back {}", bytes.len(), r(postcard::from_bytes::<Big>(&bytes)));
  // ... serde_yaml、toml、serde_urlencoded 同样写出再读回
formats, Big { n: u128::MAX } and plain u128::MAX
  serde_json  Ok("{\"n\":340282366920938463463374607431768211455}")
  json Value  Err(number out of range)
  json Value7 Ok(Object {"n": Number(7)})
  bincode     16 back Ok(Big { n: 340282366920938463463374607431768211455 })
  postcard    19 back Ok(Big { n: 340282366920938463463374607431768211455 })
  serde_yaml  Ok("n: 340282366920938463463374607431768211455\n")
    back      Ok(Big { n: 340282366920938463463374607431768211455 })
  toml        Err(u128 is not supported)
    toml 7    Err(u128 is not supported)
    toml de   Err(TOML parse error at line 1, column 5
  |
1 | n = 7
  |     ^
u128 is not supported
)
  urlencoded  Ok("n=340282366920938463463374607431768211455")
    back      Err(u128 is not supported)
格式写 u128读 u128
serde_json 1.0.154支持支持
serde_json::to_value放得进 u64 才行,否则 number out of range—
bincode 1.3.3支持(16 字节)支持
postcard 1.1.3支持(varint,19 字节)支持
serde_yaml 0.9.34支持支持
toml 0.8.23u128 is not supported,7 也不行u128 is not supported
serde_urlencoded 0.7.1支持u128 is not supported

toml 0.8.23 和它用的 toml_edit 0.22.27 源码里没有出现 u128,两个方法都是默认实现。serde_urlencoded 0.7.1 的写出一侧实现了 serialize_u128,读的一侧给值用的 Part 反序列化器只为 bool、u8…u64、i8…i64、f32、f64 写了解析(de.rs 里的 forward_parsed_value!),deserialize_u128 落到默认实现:能写不能读。axum 的 Query 和 Form 都把解析交给 serde_urlencoded::Deserializer(见 Path 与 Query),查询参数里的 u128 字段因此读不出来;axum 自己的 PathDeserializer 写了 deserialize_u128。

修法

写成字符串。 字符串经得起任何缓冲,也是 toml、查询字符串这类格式里唯一的选择。用 serde_with::DisplayFromStr:

#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
struct BigStr {
  #[serde_as(as = "DisplayFromStr")]
  n: u128,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "t")]
enum InternalStr {
  V(BigStr),
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
enum UntaggedStr {
  V(BigStr),
}
fix: u128 as a string
  internal     Ok({"t":"V","n":"340282366920938463463374607431768211455"})
  internal     Ok(V(BigStr { n: 340282366920938463463374607431768211455 }))
  untagged     Ok(V(BigStr { n: 340282366920938463463374607431768211455 }))

JavaScript 的 Number 只能精确表示 2^53 以内的整数,对外的 JSON 接口里大整数写成字符串本来就更稳。代价是格式变了:{"n":7} 这样的旧输入不再被接受(要两种都收,见 invalid type: string "1", expected u32 的 PickFirst 和手写 deserialize_any)。

避开缓冲。 只要不经过 Content,u128 就交给格式处理:

  • 外部标签(默认表示)不缓冲,见 外部标签。
  • flatten 的 struct 里,把 u128 字段挪到外层(上面的 outer+flat)。
  • 相邻标签缓冲不缓冲取决于输入里标签和内容的顺序,下面的动脑筋。
❗IMPORTANT
动脑筋:相邻标签,标签在前还是在后
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "t", content = "c")]
enum Adjacent {
  V(Big),
}

de::<Adjacent>("adj tag 1st", r#"{"t":"V","c":{"n":7}}"#);
de::<Adjacent>("adj tag 2nd", r#"{"c":{"n":7},"t":"V"}"#);

同一个值,两种键的顺序,各是 Ok 还是 u128 is not supported?答案在页末。

怎么确认没中招

  • 搜一遍 u128 / i128 字段,看它所在的类型有没有被用在 #[serde(tag = ...)]、#[serde(untagged)] 的变体里,或者被 #[serde(flatten)] 进别的 struct;再看序列化一侧有没有「内部标签的 newtype 包外部标签的 struct 变体」。
  • 往返测试要覆盖 enum 和 flatten 的那一层,不能只测内层 struct:上面的 Big 单独测完全没问题。
  • untagged 里的 u128 不会报这句,只会 no match。把输入读成 Value,再用 from_value 对各个变体单独试,能看到 u128 is not supported。
  • 配置文件和查询参数:用 toml 0.8 或 serde_urlencoded(包括 axum 的 Query、Form)读的类型里不要有 u128 / i128,或者用字符串表示。

动脑筋答案

  adj tag 1st  Ok(V(Big { n: 7 }))
  adj tag 2nd  Err(u128 is not supported at line 1 column 21)
  • 标签在前,读得回来。 生成的 visit_map 先读到 "t",知道是变体 V,接下来 "c" 的值直接按 Big 从 serde_json 读,u128 字段由 serde_json 的 deserialize_u128 处理。
  • 内容在前,读不回来。 读到 "c" 时还不知道变体,只能先把 {"n":7} 缓冲成 Content,等读到标签再用 ContentDeserializer 重放,于是撞上默认实现。位置 column 21 是整个对象的结尾,那是 serde_json 返回前补上的。
  • 两种顺序都是合法输入,哪种出现由写 JSON 的一方决定。serde 自己序列化相邻标签时总是标签在前,所以自己写、自己读的往返测试永远是绿的;对方如果按键名排序写出 JSON,"c" 就排到了 "t" 前面。相邻标签的两条路径见 内部标签与相邻标签。

小结

  • serialize_u128 / deserialize_u128 的默认实现报 u128 is not supported;Content 没有 128 位整数,ContentDeserializer 和 ContentSerializer 都用了默认实现。
  • 内部标签、flatten 进来的部分、内容在标签之前的相邻标签:读不回,连 7 都不行;untagged 只报 no match。直接读、外部标签、flatten 外层的字段不受影响。
  • 序列化一侧,内部标签或 flatten 里的外部标签 struct 变体要先缓冲字段,也会报这句。
  • toml 0.8.23 读写都不支持,serde_urlencoded 0.7.1 能写不能读(axum 的 Query、Form 因此读不了 u128),serde_json::to_value 超过 u64 报 number out of range。
  • 修法:DisplayFromStr 写成字符串,或者让 u128 不经过缓冲。

💬 关联讨论

发起讨论
加载中...