ix-schema 0.1.0

A universal meta-interface for data structures: compile-time semantic manifests bridging memory layout and serialization, with enforced schema evolution.
Documentation
  • Coverage
  • 100%
    66 out of 66 items documented0 out of 21 items with examples
  • Size
  • Source code size: 37.26 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 716.82 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 10s Average build duration of successful builds.
  • all releases: 7s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • MerlijnW70/ix-schema
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • MerlijnW70

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_schema::Ix;

#[derive(Ix)]
#[repr(C)]
struct User {
    id: u32,
    flags: u16,
}

// Layout figures come from the compiler via `core::mem`, so the manifest
// is *guaranteed* to match the real in-memory layout.
assert_eq!(User::MANIFEST.fields.len(), 2);
assert_eq!(User::MANIFEST.layout.repr, ix_schema::Repr::C);

Because the manifest is const, a driver can decide what it supports at compile time, and the dead branches are eliminated:

use ix_schema::{Driver, Ix, Repr};

struct ZerocopyDriver;
impl<T: Ix> Driver<T> for ZerocopyDriver {
    // Folds to a constant; no runtime check survives.
    const SUPPORTED: bool =
        matches!(T::MANIFEST.layout.repr, Repr::C | Repr::Transparent)
            && T::MANIFEST.is_gap_free();
}

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 ix_schema::{Ix, Upgrade};

#[derive(Ix)]
#[repr(C)]
struct DocV1 { id: u32 }

#[derive(Ix)]
#[ix(version = 2, migrate_from = DocV1)]
#[repr(C)]
struct DocV2 {
    id: u32,
    #[ix(since = 2, default = 1)]
    revision: u16,
}

#[derive(Ix)]
#[ix(version = 3, migrate_from = DocV2)]
#[repr(C)]
struct DocV3 {
    id: u32,
    revision: u16,
    #[ix(since = 3, default = true)]
    archived: bool,
}

ix_schema::migrate_chain!(DocV1 => DocV2 => DocV3);

// One call walks v1 -> v2 -> v3, applying each version's defaults in order.
let doc: DocV3 = DocV1 { id: 7 }.upgrade();

Wire compatibility is also a compile-time assertion. assert_compatible! fails the build if a carried field is moved, resized, retyped, or dropped:

ix_schema::assert_compatible!(DocV2 : DocV1); // 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

at your option.