· via Hacker News – Front Page (native)
Deser rethinks Rust serialization with an event-driven alternative to Serde
A revived 2022 experiment called Deser rebuilds Rust serialization on event-driven sinks and heap-allocated state, trading away non-self-describing formats to escape Serde's deepest design limits.

Deser, a Rust serialization library that began as a 2022 experiment and was recently picked back up, has reached a point where its author thinks it deserves attention. In a September 29 post on Armin Ronacher's blog lucumr.pocoo.org, which reached the front page of Hacker News, he argues that Serde's most persistent problems flow from its design and its stability guarantees, and that Deser keeps Serde's user experience while replacing the machinery underneath. Ronacher spent years at Sentry working on Relay, a service that processes enormous volumes of untrusted JSON, where the same Serde issues kept resurfacing. The name is literal: Deser is Serde with its two halves swapped.
Three corner cases that motivated a rewrite
The post opens with three examples. In the first, an internally tagged enum fails when serde_'s optional arbitrary_precision feature is enabled. Serde's data model has no place for arbitrary-precision numbers, so the format smuggles them through as a map with a magic key; tagged enums must buffer fields until the tag arrives, and the buffer does not know about the magic key. A perfectly valid radius value is then rejected with "invalid type: map, expected f64" — and because Cargo unifies features, one crate anywhere in the dependency graph turning the feature on is enough to trigger it.
The second involves #[serde(flatten)]. A struct holding a HashMap<u32, u32> parses JSON string keys into integers when deserialized on its own, but once the value passes through flatten's buffering, the key "42" remains a plain string and the parse fails. The error also points at the end of the document rather than at the offending key.
The third is composability. Since a function cannot be passed as a type parameter, a deserialize_with adapter such as from_hex cannot reach inside an Option, Vec or map. You write a separate wrapper function per container, and the field is no longer optional unless you remember to add #[serde(default)].
According to the post, none of these are bugs that can simply be patched: they fall out of Serde's design, which is locked in by stability promises made to every format and hand-written implementation.
Three decisions behind the pain
Ronacher traces the recurring problems to three root decisions. Serde uses a single set of traits for both self-describing formats like JSON, YAML and TOML and formats where the reader must know the type upfront, such as postcard, bincode or protobuf; that breadth is useful, but format-dependent features only reveal their limits at runtime, and a derived struct will quietly accept a JSON array where an object was expected. Serde's fixed data model loses information whenever internally tagged enums, untagged enums or flatten buffer values, which is why errors lose their location and ecosystem extensions fall back on in-band signalling. And deserialization recurses on the call stack: every nesting level consumes stack space, recursion limits guard only some code paths, and deeply nested data can take down a process through writing or dynamic-value paths that lack one. Recursion also means a deserialization cannot pause while waiting for more input.
How Deser flips the model
In Serde, the type drives: a Deserialize implementation asks the deserializer for the kind of value it expects, and the format calls back into a visitor, recursing at each level. Deser inverts this. The format announces the next value and pushes events into a sink; when a sink meets a nested value it does not recurse into it but hands a new sink to a driver, with all state kept on the heap in an arena. Emitters return nested values on the way out instead of descending into them. The trait design was originally modelled on dtolnay's miniserde, one of several earlier attempts alongside efforts built on runtime reflection or new data models aimed at binary formats.
The inversion carries a deliberate cost: Deser cannot support non-self-describing formats such as protobuf, and it leaves them out of the design entirely. As the post frames it, fixing Serde means accepting other compromises.
What it would take to challenge Serde
Ronacher does not expect Serde to be dethroned — the orphan rule entrenches it deeply in the ecosystem — so Deser competes on what a crate author can control: completeness. It implements the major self-describing formats, including YAML, JSON, TOML, CBOR, JSON5, XML and plist; XML in particular is a format Serde itself has declined to support. The tradeoffs are tangible. Compile times are somewhat better than Serde's, binary bloat is considerably worse, and runtime performance is mixed — roughly comparable overall, though some format structures lose significantly. The post's verdict is that Deser is now, in principle, a drop-in replacement where those tradeoffs fit.
Why it matters
Serde is load-bearing infrastructure for the Rust ecosystem, and its limits shape how every crate that touches JSON, YAML or TOML behaves. Deser demonstrates that the familiar derive-based experience can sit on an event-driven, heap-based core, sidestepping whole classes of problems — buffering that loses information, stack exhaustion on untrusted input, adapters that cannot compose — that Serde's stability guarantees put out of reach. Even if it never displaces Serde, the experiment doubles as a map of the design space, and a reminder that the dominant tool's constraints are choices rather than laws.
- #rust
- #serialization
- #serde
- #deser
- #libraries