Deser:用堆積上的 sink 取代遞迴 visitor 的 Rust 序列化庫
Armin Ronacher 個人部落格 · 2026-09-29
Serde 的 visitor 由型別驅動、逐層遞迴呼叫,Deser 把方向反過來:由格式把事件推進 sink,巢狀狀態放在堆積上。作者 Armin Ronacher 在 2022 年起手這個實驗,擱置多年後重新拾起,並在 2026-09-29 發文介紹(mitsuhiko/deser)。
表面用法幾乎與 Serde 相同,差別在底層架構,以及作者為此承認的代價。
原本的問題
作者列出三個 Serde 的邊角案例,都是功能之間的交互作用,而不是單純 bug。arbitrary_precision 打開後,內部標記的 enum 會在解析 f64 時報 invalid type: map, expected f64,因為 serde_json 用帶魔術 key 的 map 夾帶大數,而 enum 的緩衝區不認得它。Cargo feature 會被整個依賴圖統一,任何一個 crate 開啟就足以觸發。
flatten 會弄壞整數 key:HashMap<u32, u32> 單獨解析 {"scores": {"42": 23}} 沒問題,一旦包進 #[serde(flatten)],緩衝後的 "42" 只剩字串,錯誤位置還指向文件結尾。
轉接函式無法組合:deserialize_with 只能套在整個欄位上,函式不能當型別參數,所以 Option<u32> 得另寫 from_opt_hex,欄位也不再自動可省略。
作者把根源歸結為三個設計:所有格式共用同一組 trait、固定資料模型在緩衝時會遺失資訊、遞迴佔用呼叫堆疊。這些都受 Serde 穩定性保證保護,無法在不破壞所有格式與手寫實作的前提下修掉。
核心改動
| Serde | Deser | |
|---|---|---|
| 驅動方 | 型別呼叫 deserializer,格式回呼 visitor | 格式推事件進 sink,driver 管理狀態 |
| 巢狀 | 遞迴,佔用呼叫堆疊 | sink 交回 driver,放堆積(arena) |
| 特殊值 | 魔術 key 的 map | extension value,附 fallback |
| 緩衝 | 資料模型有損 | 連同格式資訊一起記錄 |
這套 sink / emitter 設計「完全偷自 miniserde」。好處是不會 stack overflow,限制交給 layer 設定;解析可暫停續餵且 Send,適合 tokio;flatten 原生支援,不需緩衝。代價是不支援 protobuf 這類非自描述格式,作者刻意排除。
#[derive(Deserialize)]
struct Config {
#[deser(as = Check<NonEmpty, Vec<Hex>>)]
secret_keys: Vec<[u8; 32]>,
#[deser(as = Option<Vec<DisplayFromStr>>)]
allowed_networks: Option<Vec<IpNet>>,
}屬性接受真正的 Rust 表達式而非字串,adapter 是型別,所以 Option<Vec<DisplayFromStr>> 可以直接巢狀。加上 PathLayer 後,錯誤會帶路徑,例如 path: listeners[1].port,即使 enum tag 排在最後、值必須先緩衝也不會丟失位置。
XML 與 TOML 的實例
作者以 Atom 加 Dublin Core 為例:quick-xml 忽略命名空間,且重複的 creator 元素在 untagged enum 內會被緩衝成 map,只留最後一個,John 會無聲消失。Deser 依命名空間比對,兩位作者都會保留。TOML 的 datetime 在 serde_json::Value 裡會變成 $__toml_private_datetime 的魔術 key map,在 Deser 中則是 extension value,不認識的格式一律寫成字串。
影響範圍
若你處理不受信任的深層巢狀輸入(作者的背景是 Sentry Relay),或需要 XML、命名空間、串流解析,值得評估。若你依賴 protobuf、bincode 這類格式,或對二進位大小敏感,不適用。
- JSON 讀取:依資料介於快 33% 到慢 60%,平均約慢 10%。
- JSON 寫入:介於快三倍到慢 70%,平均持平。
- release 建置的 derive 程式碼編譯約快 2.3 倍,但二進位膨脹更明顯。
- 內部使用
unsafe維持堆積上的 borrowed sink 鏈。
作者本人也認為由於 orphan rule,Serde 難以被取代;Deser 的目標是格式覆蓋完整,使「缺格式」不再是不用它的理由。已支援 JSON、JSONC、JSON5、HJSON、CBOR、MessagePack、YAML、TOML、XML、plist、CSV 與 urlencoded 等。文中另有 deser-serde crate 可橋接 Serde,細節文章未展開。