ix-schema
A universal meta-interface for data structures.
ix-schema does not serialize anything itself. It is an orchestrator: a
#[derive(Ix)] type publishes a compile-time manifest describing its
fields, memory layout, and schema evolution. Drivers — thin adapters around
serde, zerocopy, and friends — read that manifest and do the real work,
branching on const data that folds away. No reflection, no runtime schema
parsing, no cost.
The core crate is #![no_std] and #![forbid(unsafe_code)]. Everything the
manifest carries is const, so it is free at runtime.
Why
Two facts about a struct usually live in different worlds: its memory layout
(what zerocopy cares about) and its schema (what serde and your wire
format care about). ix-schema makes both a single, compiler-computed value:
use Ix;
// Layout figures come from the compiler via `core::mem`, so the manifest
// is *guaranteed* to match the real in-memory layout.
assert_eq!;
assert_eq!;
Because the manifest is const, a driver can decide what it supports at compile
time, and the dead branches are eliminated:
use ;
;
Schema evolution, type-checked
Versions are wired with migrate_from. Each edge is generated field-by-field,
so an impossible transformation is a type error, not a runtime panic. A whole
chain composes into a direct oldest-to-newest upgrade:
use ;
migrate_chain!;
// One call walks v1 -> v2 -> v3, applying each version's defaults in order.
let doc: DocV3 = DocV1 .upgrade;
Wire compatibility is also a compile-time assertion. assert_compatible! fails
the build if a carried field is moved, resized, retyped, or dropped:
assert_compatible!; // append-only — OK
Field attributes
Struct-level #[ix(...)]:
| attribute | meaning |
|---|---|
version = N |
schema version (default 1) |
migrate_from = T |
this version evolved from type T |
removed("a", "b") |
fields dropped relative to the predecessor |
Field-level #[ix(...)]:
| attribute | meaning |
|---|---|
since = N |
version the field was introduced in |
default = EXPR |
value for a field absent in the previous version |
with = PATH |
function converting the predecessor's field |
rename_from = "old" |
the field's name in the previous version |
Crates
| crate | role |
|---|---|
ix-schema |
the core trait, the const manifest, and the migration plumbing |
ix-schema-derive |
the #[derive(Ix)] proc-macro |
ix-schema-serde |
serde driver: version-tagged JSON and migrate/upgrade-on-decode |
ix-schema-postcard |
postcard driver: the same, over compact binary instead of JSON |
ix-schema-zerocopy |
zerocopy driver: manifest-validated, zero-cost byte views |
ix-schema-serde (self-describing JSON) and ix-schema-postcard (compact binary) are the same
version-and-migration logic over two different codecs — the manifest contract is
format-agnostic, so further backends drop in the same way.
Status
0.1 — the core model, the derive, and both drivers are implemented and tested.
The derive supports named-field structs and enums — both fieldless and
data-carrying (variant payloads are described by type, though variant-payload
byte offsets are not modelled, as Rust exposes no const access to them).
Unions and tuple structs are not yet modelled.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.