Deser:重新思考 Rust 序列化

Deser:重新思考 Rust 序列化

💡 原文英文,约2700词,阅读约需10分钟。
📝

内容提要

Serde 是 Rust 主流序列化库,但存在设计局限:内部标签枚举与任意精度数字冲突、flatten 缓冲丢失类型信息、无法对 Option 等包装类型应用自定义函数。作者为此开发了 Deser,采用事件驱动、堆上 sink 架构,避免递归,支持 XML 命名空间、扩展值和路径错误定位。代价是运行时性能略慢、二进制体积更大,且生态兼容性有限。

🔎

延伸解读

Serde 的三大设计局限

文章指出 Serde 的局限并非偶然 bug,而是源于三个核心设计决策:所有格式共用一套 trait,导致部分特性仅对特定格式有效且运行时才暴露;固定数据模型在缓冲时丢失信息,内部标签枚举、flatten 等场景下错误位置丢失,任意精度数字依赖带内信号;递归调用栈使嵌套深度受限于栈空间,且无法暂停反序列化等待更多输入。这些设计受稳定性保证保护,难以在不破坏生态的情况下修复。

Deser 的架构转向与代价

Deser 将 Serde 的“类型驱动”反转为“格式驱动”:解析器推送事件到堆上 sink,嵌套值通过 driver 管理,避免递归。这带来了 XML 命名空间、扩展值、路径错误定位等能力,但也付出代价:运行时性能混合,JSON 读取平均慢约 10%,写入平均持平;二进制体积更大;内部使用 unsafe 管理堆上借用链。此外,Deser 无法支持 protobuf 等非自描述格式,这是有意为之的取舍。

XML 与扩展值的实际差异

文章以 XML 为例展示 Deser 与 Serde 生态的差异:quick-xml 忽略命名空间,可能错误接受其他命名空间的元素;处理重复元素需开启全局 overlapped-lists 特性,且缓冲后仍可能失败或静默丢失数据。Deser 则通过命名空间宏和缓冲区保留所有值,正确收集多个 creator。对于 TOML 日期时间等格式特定类型,Deser 将其作为扩展值,跨格式转换时保留语义,而 Serde 依赖魔法键的 map 表示,易导致类型错误。

生态兼容性与适用场景

Deser 并非 Serde 的替代品,作者明确表示由于孤儿规则,Serde 在生态中根深蒂固。Deser 目前支持 JSON、YAML、TOML、XML、plist 等自描述格式,并可作为 drop-in 替代,但性能与二进制体积的权衡因场景而异。对于处理大量不可信 JSON、需要 XML 命名空间或路径错误定位的场景,Deser 可能值得尝试;而对于性能敏感或依赖 protobuf 等格式的项目,则需谨慎评估。

❓

Q&A

Serde 有哪些设计上的局限性?

Serde 存在三个主要设计局限:1. 内部标签枚举与任意精度数字冲突,因为 Serde 数据模型无法表示任意精度数字,serde_json 使用带魔术键的 map 进行带内信号传递,导致枚举缓冲字段时无法识别魔术键;2. flatten 缓冲会丢失类型信息,例如 JSON 键原本可转为整数,但缓冲后变成字符串,且错误位置指向文档末尾而非具体键;3. 无法对 Option、Vec 等包装类型应用自定义函数,因为函数不能作为类型参数传递,需要为每个包装类型编写单独函数。

Deser 是什么?它的核心设计思路是什么?

Deser 是一个 Rust 序列化库,旨在提供类似 Serde 的用户体验,但采用完全不同的架构,灵感来自 miniserde。其核心设计是事件驱动、堆上 sink 架构:格式告诉类型下一个值的类型并将事件推入 sink,遇到嵌套值时 sink 返回新的 sink 给 driver,所有状态保存在堆(arena)中,避免递归。这种设计不支持非自描述格式(如 protobuf),且无法暂停反序列化等待更多输入。

Deser 相比 Serde 在性能上有什么代价?

Deser 的运行时性能略慢:JSON 读取平均比 serde_json 慢约 10%(范围从快 33% 到慢 60%),写入平均持平(范围从快 3 倍到慢 70%)。二进制体积更大,但编译时间略好,派生代码的 release 构建快约 2.3 倍。此外,Deser 内部使用 unsafe 来维护堆上借用 sink 链。

Deser 如何处理 XML 命名空间和扩展值?

Deser 支持 XML 命名空间,通过命名空间而非前缀匹配元素名,例如 atom!("title") 展开为 {http://www.w3.org/2005/Atom}title。它还能将多个同命名空间元素收集到 Vec 中,即使中间有其他元素。对于扩展值(如 TOML 日期时间),Deser 将其作为扩展值保留,格式认识则保留,否则写为字符串,避免了 Serde 中魔术键 map 的问题。

Deser 的适配器(adapters)和验证器有什么特点?

Deser 的适配器是类型,可以嵌套使用,例如 Hex 可放在 Vec 内,DisplayFromStr 可放在 Option<Vec<...>> 内。验证器也是适配器,如 Check<NonEmpty, Vec<Hex>> 先解码再检查非空。这解决了 Serde 无法对包装类型应用自定义函数的问题。

Deser 支持哪些数据格式?

Deser 支持所有重要的自描述格式:JSON、JSONC、JSON5、HJSON、YAML 1.1/1.2、TOML、CBOR、MessagePack、XML、plist(三种变体)、CSV/TSV、urlencoded 数据、环境变量等。还支持路径信息、位置捕获、调试打印、解析时验证、多种二进制编码、桥接 Serde、动态值捕获、格式转码和 tokio 集成。

🏷️

标签

➡️

继续阅读