Contexte et limites de Serde

Serde, bibliothèque phare de sérialisation Rust, présente trois cas d’utilisation problématiques. Le premier montre qu’un nombre décimal de précision arbitraire, activé via serde_json::arbitrary_precision, est encodé sous forme de carte contenant une clé magique ; l’enum interne Shape échoue car le tampon ne reconnaît pas cette clé (

#[derive(Deserialize)]
#[serde(tag = "type")]
enum Shape { Circle { radius: f64 }, }
serde_json::from_str::(r#"{"type":"Circle","radius":1.5}"#) // error: invalid type: map, expected f64
).

Le deuxième problème apparaît avec flatten sur une HashMap : les clés JSON, toujours des chaînes, restent des chaînes dans le tampon, ce qui provoque une erreur de type (

#[derive(Deserialize)]
struct Stats { scores: HashMap, }
#[derive(Deserialize)]
struct Report { name: String, #[serde(flatten)] stats: Stats, }
serde_json::from_str::(r#"{"name":"x","scores":{"42":23}}"#) // error: invalid type: string "42", expected u32
).

Le troisième cas expose l’impossibilité de composer des adaptateurs : une fonction de désérialisation personnalisée from_hex ne peut pas être appliquée automatiquement à Option ou à des collections, obligeant l’écrivain à créer des wrappers spécifiques (

fn from_hex<'de, D: Deserializer<'de>>(d: D) -> Result { … }
#[derive(Deserialize)]
struct Theme { #[serde(deserialize_with = "from_hex")] primary: u32, #[serde(deserialize_with = "from_hex")] accent: Option, }
// error[E0308]: expected `Option`, found `u32`
).

Ces limites découlent de trois décisions de conception : un jeu unique de traits pour tous les formats, un modèle de données fixe qui perd des informations lors du tamponnage, et une récursion de pile pour chaque niveau de profondeur. La stabilité de l’API de Serde empêche de corriger ces comportements sans rupture majeure.

Architecture et fonctionnement de Deser

Deser renverse le modèle de Serde : au lieu que le type interroge le désérialiseur, le format pousse des événements vers un « sink ». Chaque événement représente le type de la prochaine valeur et le sink renvoie un nouveau sink lorsqu’une structure imbriquée commence. Tous les états sont conservés dans un arena heap, éliminant la croissance de la pile. Cette approche empêche les dépassements de pile lors de la désérialisation de documents profondément imbriqués et rend possible la pause du processus en attendant davantage d’entrée.

Le design exclut explicitement les formats non auto‑descriptifs comme protobuf, car ils nécessitent que le lecteur connaisse le type à l’avance. En se concentrant sur les formats auto‑descriptifs (JSON, YAML, TOML), Deser évite les compromis liés aux traits universels de Serde et supprime la nécessité de tamponner des valeurs avant de connaître leur destination.

Analyse des compromis et impacts

Le passage à un modèle événementiel améliore la prévisibilité des erreurs : chaque événement porte son emplacement source, ce qui corrige le problème de localisation vu avec flatten. En outre, l’absence de récursion de pile réduit le risque de plantage du processus sous des charges de données massives, un scénario fréquent chez Sentry Relay.

Cependant, le choix d’exclure les formats non auto‑descriptifs limite l’adoption de Deser dans les systèmes qui privilégient la compacité binaire (par exemple, protobuf ou bincode). De plus, la dépendance à un arena heap introduit une surcharge de gestion mémoire et peut augmenter le temps de désallocation comparé à la libération de pile immédiate.

Enfin, le découplage des adaptateurs de type signifie que les fonctions de désérialisation personnalisées restent simples : elles s’appliquent directement à la valeur cible sans nécessiter de wrappers supplémentaires. Cette simplicité réduit le code boilerplate, mais elle impose aux développeurs de choisir entre la flexibilité de Serde et la robustesse de Deser selon leurs exigences de format et de performance.