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
//! Packed structs on their home turf: a wide, sparsely-populated record —
//! telemetry with 16 possible fields, only 4 set. Compares wire size and
//! access speed for Veritate (packed vs sparse), protobuf (prost), and JSON.
//!
//!   cargo run --release --example packed
//!
//! Packed recovers protobuf-class wire size on sparse data while keeping O(1)
//! zero-copy random access (protobuf must scan-decode the whole message).

use std::hint::black_box;
use std::time::{Duration, Instant};

use prost::Message as _;
use serde::{Deserialize, Serialize};
use verit::{encode, Dt, Message, Resolver, Schema, SchemaBuilder, SchemaMode, Value};

const N: u16 = 16;
/// The (1-based) ids that are actually present, and their values. Large
/// values on purpose: telemetry fields are usually ids/timestamps/hashes near
/// the top of the range, exactly where protobuf's varints lose their edge.
const SET: &[(u16, u64)] = &[
    (2, 0xF000_0000_0000_0002),
    (7, 0xF000_0000_0000_0007),
    (11, 0xF000_0000_0000_000B),
    (15, 0xF000_0000_0000_000F),
];

fn veritate_schema(packed: bool) -> Schema {
    let fields: Vec<(u16, String, Dt)> =
        (1..=N).map(|i| (i, format!("f{i:02}"), Dt::U64)).collect();
    let fields_ref: Vec<(u16, &str, Dt)> = fields
        .iter()
        .map(|(i, n, t)| (*i, n.as_str(), t.clone()))
        .collect();
    let b = SchemaBuilder::new();
    let b = if packed {
        b.add_packed_struct("Telemetry", fields_ref)
    } else {
        b.add_struct("Telemetry", fields_ref)
    };
    b.build("Telemetry").unwrap()
}

fn veritate_value() -> Value {
    Value::Struct(SET.iter().map(|(id, v)| (*id, Value::U64(*v))).collect())
}

/// Protobuf message with 16 optional uint64 fields — only set ones hit the wire.
#[derive(Clone, PartialEq, prost::Message)]
struct PbTelemetry {
    #[prost(uint64, optional, tag = "1")]
    f01: Option<u64>,
    #[prost(uint64, optional, tag = "2")]
    f02: Option<u64>,
    #[prost(uint64, optional, tag = "3")]
    f03: Option<u64>,
    #[prost(uint64, optional, tag = "4")]
    f04: Option<u64>,
    #[prost(uint64, optional, tag = "5")]
    f05: Option<u64>,
    #[prost(uint64, optional, tag = "6")]
    f06: Option<u64>,
    #[prost(uint64, optional, tag = "7")]
    f07: Option<u64>,
    #[prost(uint64, optional, tag = "8")]
    f08: Option<u64>,
    #[prost(uint64, optional, tag = "9")]
    f09: Option<u64>,
    #[prost(uint64, optional, tag = "10")]
    f10: Option<u64>,
    #[prost(uint64, optional, tag = "11")]
    f11: Option<u64>,
    #[prost(uint64, optional, tag = "12")]
    f12: Option<u64>,
    #[prost(uint64, optional, tag = "13")]
    f13: Option<u64>,
    #[prost(uint64, optional, tag = "14")]
    f14: Option<u64>,
    #[prost(uint64, optional, tag = "15")]
    f15: Option<u64>,
    #[prost(uint64, optional, tag = "16")]
    f16: Option<u64>,
}

fn pb_value() -> PbTelemetry {
    let mut m = PbTelemetry::default();
    for (id, v) in SET {
        match id {
            2 => m.f02 = Some(*v),
            7 => m.f07 = Some(*v),
            11 => m.f11 = Some(*v),
            15 => m.f15 = Some(*v),
            _ => unreachable!(),
        }
    }
    m
}

#[derive(Default, Serialize, Deserialize)]
struct JTelemetry {
    #[serde(skip_serializing_if = "Option::is_none")]
    f02: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    f07: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    f11: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    f15: Option<u64>,
}

fn json_value() -> JTelemetry {
    JTelemetry {
        f02: Some(SET[0].1),
        f07: Some(SET[1].1),
        f11: Some(SET[2].1),
        f15: Some(SET[3].1),
    }
}

fn bench(name: &str, mut f: impl FnMut() -> u64) {
    let mut n: u64 = 1;
    let mut elapsed;
    loop {
        let t = Instant::now();
        let mut acc = 0u64;
        for _ in 0..n {
            acc = acc.wrapping_add(f());
        }
        black_box(acc);
        elapsed = t.elapsed();
        if elapsed >= Duration::from_millis(150) || n >= 1 << 28 {
            break;
        }
        n *= 2;
    }
    let mut best = elapsed.as_nanos() as f64 / n as f64;
    for _ in 0..3 {
        let t = Instant::now();
        let mut acc = 0u64;
        for _ in 0..n {
            acc = acc.wrapping_add(f());
        }
        black_box(acc);
        best = best.min(t.elapsed().as_nanos() as f64 / n as f64);
    }
    println!("  {name:<38} {best:>10.1} ns/op");
}

fn main() {
    let packed = veritate_schema(true);
    let sparse = veritate_schema(false);
    let vp = encode(&packed, &veritate_value(), SchemaMode::HashOnly).unwrap();
    let vs = encode(&sparse, &veritate_value(), SchemaMode::HashOnly).unwrap();
    let pb = pb_value().encode_to_vec();
    let js = serde_json::to_vec(&json_value()).unwrap();

    println!(
        "sparse wide record: {} of {} fields set (u64 each)\n",
        SET.len(),
        N
    );
    // The 32-byte envelope (magic, flags, 16-byte schema id, root offset) is a
    // fixed per-message cost; the block sizes are what the layout controls.
    let env = verit::encode::HEADER_LEN;
    println!("--- wire size (bytes; block = payload without the 32B envelope) ---");
    println!(
        "  veritate (packed)      {:>5}   block {:>4}",
        vp.len(),
        vp.len() - env
    );
    println!(
        "  veritate (sparse)      {:>5}   block {:>4}",
        vs.len(),
        vs.len() - env
    );
    println!("  protobuf (prost)       {:>5}", pb.len());
    println!("  json                   {:>5}", js.len());
    println!(
        "\npacked block is {}% smaller than sparse ({} -> {} bytes) — the win the",
        (100 * ((vs.len() - env) - (vp.len() - env))) / (vs.len() - env),
        vs.len() - env,
        vp.len() - env,
    );
    println!(
        "feature exists for. Note the packed block ({}B) even undercuts protobuf's\n\
         varints ({}B) here: fixed-width slots beat varints once values are large\n\
         (ids, timestamps, hashes). Protobuf's total edge is entirely Veritate's\n\
         fixed {}B envelope — the price of a self-describing, evolvable message\n\
         that also keeps O(1) zero-copy random access (protobuf must scan-decode).",
        vp.len() - env,
        pb.len(),
        env,
    );

    let resolver = Resolver::identity(&packed).unwrap();
    println!("\n--- random access: bytes -> read fields 11 and 15 ---");
    bench("veritate packed (popcount, O(1))", || {
        let msg = Message::parse(&vp).unwrap();
        let root = msg.root(&resolver).unwrap();
        root.get_u64(11).unwrap().unwrap() + root.get_u64(15).unwrap().unwrap()
    });
    bench("protobuf (must decode whole msg)", || {
        let m = PbTelemetry::decode(&pb[..]).unwrap();
        m.f11.unwrap() + m.f15.unwrap()
    });
    bench("json (must decode whole msg)", || {
        let m: JTelemetry = serde_json::from_slice(&js).unwrap();
        m.f11.unwrap() + m.f15.unwrap()
    });
    println!(
        "(on this tiny record protobuf's full decode is nearly free; packed's O(1)\n\
         access pulls ahead as records grow — see the benchmarks doc for the large case.)"
    );

    // Sanity: every representation agrees.
    let msg = Message::parse(&vp).unwrap();
    let root = msg.root(&resolver).unwrap();
    let vsum: u64 = SET
        .iter()
        .map(|(id, _)| root.get_u64(*id).unwrap().unwrap())
        .sum();
    let expected: u64 = SET.iter().map(|(_, v)| v).sum();
    assert_eq!(vsum, expected);
    println!("\nall present fields read back correctly (sum {expected})");
}