verit 0.2.0

Exavian Veritate — zero-copy, self-describing, schema-evolvable binary serialization, safe on untrusted bytes, no unsafe, byte-identical across independent implementations.
Documentation
//! Proof obligation 4b: the entire read path — parse header, open root, load
//! every field — performs zero heap allocations.
//!
//! This test lives alone in its own binary: the allocation counter is a
//! process-wide global, so any sibling test running on another thread would
//! pollute the measurement.

use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};

use verit::{encode, Dt, Message, Ref, Resolver, SchemaBuilder, SchemaMode, Value};

struct CountingAlloc;

static ALLOCATIONS: AtomicUsize = AtomicUsize::new(0);

unsafe impl GlobalAlloc for CountingAlloc {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
        // SAFETY: forwarding the caller's valid `layout` to the system allocator.
        unsafe { System.alloc(layout) }
    }
    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        // SAFETY: `ptr`/`layout` come from a prior `alloc` with the same layout.
        unsafe { System.dealloc(ptr, layout) }
    }
}

#[global_allocator]
static ALLOC: CountingAlloc = CountingAlloc;

#[test]
fn read_path_performs_zero_allocations() {
    let schema = SchemaBuilder::new()
        .add_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .add_struct(
            "Doc",
            vec![
                (1, "title", Dt::Str),
                (2, "count", Dt::U64),
                (3, "points", Dt::list(Dt::named("Point"))),
            ],
        )
        .build("Doc")
        .unwrap();
    let bytes = encode(
        &schema,
        &Value::Struct(vec![
            (1, Value::str("alloc-free")),
            (2, Value::U64(42)),
            (
                3,
                Value::List(vec![
                    Value::Struct(vec![(1, Value::F64(1.0)), (2, Value::F64(2.0))]),
                    Value::Struct(vec![(1, Value::F64(3.0)), (2, Value::F64(4.0))]),
                ]),
            ),
        ]),
        SchemaMode::HashOnly,
    )
    .unwrap();
    // Plan built once, ahead of time — this is the per-schema-pair cost.
    let resolver = Resolver::identity(&schema).unwrap();

    // The hot path: everything from raw bytes to field values.
    let before = ALLOCATIONS.load(Ordering::Relaxed);
    let msg = Message::parse(&bytes).unwrap();
    let root = msg.root(&resolver).unwrap();
    let title = root.get_str(1).unwrap().unwrap();
    let count = root.get_u64(2).unwrap().unwrap();
    let points = root.get_list(3).unwrap().unwrap();
    let mut sum = 0.0f64;
    for i in 0..points.len() {
        if let Ref::Struct(p) = points.get(i).unwrap() {
            sum += p.get_f64(1).unwrap().unwrap() + p.get_f64(2).unwrap().unwrap();
        }
    }
    let after = ALLOCATIONS.load(Ordering::Relaxed);

    assert_eq!(title, "alloc-free");
    assert_eq!(count, 42);
    assert_eq!(sum, 10.0);
    assert_eq!(
        after - before,
        0,
        "zero-copy read path must not touch the heap"
    );
}