Skip to content

egare0/zerec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zerec

Minimal zero-copy binary codec for Rust.

No schema files. No runtime reflection. No type tags on the wire. Just bytes.

Quick start

[dependencies]
zerec = { version = "0.3", features = ["derive"] }
use zerec::{Encode, Decode, codec::{to_bytes, from_bytes}};

#[derive(Encode, Decode, Debug, PartialEq)]
struct Bullet {
    origin: [f32; 3],
    speed:  f32,
    damage: u32,
}

fn main() {
    let b = Bullet { origin: [1.0, 0.0, -3.5], speed: 900.0, damage: 42 };
    let bytes = to_bytes(&b);
    assert_eq!(from_bytes::<Bullet>(&bytes).unwrap(), b);
}

Features

Feature What it enables
derive #[derive(Encode, Decode)] proc macros
glam Encode/Decode for glam::Vec2/3/4, Quat, Mat4

no_std

zerec works in no_std + alloc environments. Register a global allocator and it just works — no feature flags needed.

std is not required; alloc is.

Wire format (ZRC)

Tag-free, little-endian, tightly packed. No padding, no alignment waste.

Type Wire size
u8 / i8 1 byte
u16 / i16 2 bytes
u32 / i32 / f32 4 bytes
u64 / i64 / f64 8 bytes
bool 1 byte (0x00 / 0x01)
char 4 bytes (Unicode scalar as u32)
[T; N] N * size_of(T) — no length prefix
Vec<T> u32 element count + elements
String / &str u32 byte length + UTF-8 bytes
Option<T> 1 tag byte + optional payload
enum u32 variant index + payload
struct fields in declaration order

Field order is the contract. Adding or reordering fields is a breaking change.

Field attributes

#[derive(Encode, Decode)]
struct Example {
    // Exclude from the wire; filled with Default::default() on decode.
    #[zerec(skip)]
    cache: u64,

    // Route through an Adapter for foreign types.
    #[zerec(via = "RigidBodyAdapter")]
    body: RigidBody,

    // Inline closures for quick conversions.
    #[zerec(
        map_enc = "|v: &[f32; 3]| *v",
        map_dec = "|dec| {
            let a: [f32; 3] = zerec::Decode::decode(dec)?;
            Ok(a)
        }"
    )]
    position: [f32; 3],
}

Adapter pattern

For types you don't own:

use zerec::Adapter;

struct RigidBody { mass: f32, vel: [f32; 3] }
struct RigidBodyAdapter;

impl Adapter<RigidBody> for RigidBodyAdapter {
    type Repr = (f32, [f32; 3]);
    fn to_repr(v: &RigidBody) -> Self::Repr { (v.mass, v.vel) }
    fn from_repr(r: Self::Repr) -> RigidBody { RigidBody { mass: r.0, vel: r.1 } }
}

Zero-copy reads

Borrow &str and &[u8] straight from the source buffer, no allocation:

use zerec::{ZeroBuf, decoder::BufDecoder};

fn main() {
    let mut dec = BufDecoder::new(&bytes);
    let name: &str = ZeroBuf::decode_borrowed(&mut dec).unwrap();
}

Without derive

Manual impl if you need fine-grained control:

use zerec::{Encode, Decode, encoder::BufEncoder, decoder::BufDecoder, error::DecodeError};

struct Color { r: u8, g: u8, b: u8 }

impl Encode for Color {
    fn encode(&self, enc: &mut BufEncoder) {
        self.r.encode(enc);
        self.g.encode(enc);
        self.b.encode(enc);
    }
}

impl Decode for Color {
    fn decode(dec: &mut BufDecoder<'_>) -> Result<Self, DecodeError> {
        Ok(Self {
            r: u8::decode(dec)?,
            g: u8::decode(dec)?,
            b: u8::decode(dec)?,
        })
    }
}

Workspace

zerec/
├── zerec/          # core library
└── zerec-derive/   # proc-macro crate (used via feature = "derive")

License

Licensed under either of Apache License 2.0 or MIT License at your option.

About

Minimal zero-copy binary codec for Rust.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages