Should OCaml's Marshal module include a version header for forward‑compatible backup restoration?
0 reputation · 20 Aug 2020, 14:04 UTC
0 reputation · 20 Aug 2020, 14:04 UTC
Goal: guarantee that a marshaled value created with one OCaml compiler version can be safely deserialized by a later version without silent corruption or Marshal.Error.
Constraint: the current Marshal format contains no version field, so differences in the binary layout between releases can cause deserialization to fail or reconstruct an incorrect value; additionally, marshalling is not portable across 32‑/64‑bit architectures and poses code‑execution risks with untrusted data.
Uncertainty: the OCaml community has not agreed on whether to extend Marshal with a version header to provide forward‑compatible guarantees, and if so, what format and placement would minimize overhead while preserving existing behavior.
What minimal version identifier could be added without breaking existing marshaled blobs? How would the header affect round‑trip size and deserialization speed? What migration strategy would allow older code to continue reading blobs produced before the change?
34775 reputation · 20 Aug 2020, 22:59 UTC
Adding a version header directly to the Marshal format would give a clear forward‑compatibility marker, but it also introduces the risk of breaking every existing marshaled blob. In practice, the safest path is to keep the current format unchanged and add a lightweight wrapper that carries the version (and, optionally, word‑size or checksum) outside the Marshal payload. This preserves backward compatibility while still providing the desired safety guarantees.
Marshal.Error or, worse, silent corruption.Conceptually, a single‑byte version number inserted immediately after the current four‑byte magic number would add at most four bytes of overhead. The payload would remain unchanged, so existing serialisers would still produce the same data after the magic bytes. A reader that understands the new header could simply skip the byte if it sees the magic, while older readers would treat the byte as part of the payload and therefore fail.
Rather than changing Marshal itself, you can wrap the Marshal payload in a small envelope containing a magic string, a version byte, and optional metadata. The envelope is independent of the Marshal layout, so it can coexist with any existing blob. Readers that know the envelope can strip it before calling Marshal.from_bytes; readers that do not recognize the envelope simply treat the data as a raw Marshal blob. This approach preserves total backward compatibility and keeps the change isolated to your application code.
In short, a version header is technically possible and would give a clear forward‑compatibility signal, but changing the Marshal format carries a high risk of breaking legacy blobs. A lightweight wrapper that adds a magic string, a version byte, and optional metadata is a safer, backward‑compatible alternative that still provides the intended safety guarantees. The choice depends on your specific versioning, architecture, and consumer constraints.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.