Skip to content

Xanthorrhizol/xancode

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

xancode

A minimal binary codec for Rust structs, generated by a derive macro.

Usage

cargo add xancode
use xancode::Codec;

#[derive(Codec)]
struct Greeting {
    id: u32,
    text: String,
    tags: Vec<String>,
}

let msg = Greeting {
    id: 1,
    text: "hi".into(),
    tags: vec!["friendly".into()],
};

let bytes = msg.encode();
let decoded = Greeting::decode(&bytes).unwrap();

Wire format

Every encoded value is length-prefixed:

[ payload_length: u32 BE ][ payload ]

Inside the payload, each field is laid out in declaration order:

Type Encoding
u8..u128, i8..i128 big-endian bytes
f32, f64 big-endian bytes (IEEE 754)
bool 1 byte (0 = false, 1 = true)
String u32 BE length + UTF-8 bytes
Vec<T> u32 BE element count + each T
HashSet<T>, BTreeSet<T> u32 BE element count + each T
HashMap<K, V>, BTreeMap<K, V> u32 BE entry count + each (K, V) pair
Option<T> 1-byte tag (0 = None, 1 = Some) + T if Some
Nested #[derive(Codec)] struct its own length-prefixed encoding
#[derive(Codec)] enum 1-byte variant tag + variant fields (see below)

⚠️ Hash collection determinism

HashMap and HashSet iterate in non-deterministic order, so encoding the same logical value twice can produce different byte sequences. Round-trips and == comparisons still work. If you hash, checksum, dedupe, or diff the wire bytes, use BTreeMap / BTreeSet — they iterate in sorted order and produce stable output.

Enums

Codec can be derived on enums. Variants are tagged by their declaration order (0, 1, 2, ...) as a single u8, followed by the variant's fields encoded in order:

#[derive(Codec)]
enum Event {
    Tick,                                 // tag 0, no payload
    Click { x: i32, y: i32 },             // tag 1, then x, then y
    Resize(u32, u32),                     // tag 2, then both u32s
}

All three variant kinds (unit, tuple, struct) are supported. Limit: up to 256 variants per enum.

⚠️ Wire stability

Variant tags are positional, not based on Rust's discriminant values or variant names. Reordering variants, removing one, or inserting a non-trailing variant breaks compatibility with already-serialized data. Treat the variant list as an append-only schema once it's in the wild.

Supported field types

Path types only: the primitives above, String, Vec<T>, HashSet<T>, BTreeSet<T>, HashMap<K, V>, BTreeMap<K, V>, Option<T>, and any other type implementing Codec (including enums and nested structs).

References, tuples, fixed-size arrays, and generics on the deriving type are not supported. Empty enums and unit / tuple structs cannot derive Codec.

Errors

encode always succeeds. decode returns Result<Self, Box<dyn Error>> and errors on truncated input, invalid Option tags, unknown enum tags, invalid bool values, or invalid UTF-8.

About

Simple encoder

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages