#framework #serialization-framework #medium #vanilla #define #deserialize

bin+lib serac

A static, modular, and light-weight serialization framework

7 unstable releases (3 breaking)

Uses new Rust 2024

0.4.2 Apr 2, 2026
0.4.1 Mar 7, 2026
0.3.0 Mar 3, 2026
0.2.1 Jan 10, 2026
0.1.0 Jan 10, 2026

#865 in Encoding

Download history 9/week @ 2026-01-08 4/week @ 2026-01-15 64/week @ 2026-01-22 15/week @ 2026-01-29 98/week @ 2026-02-05 127/week @ 2026-02-12 100/week @ 2026-02-19 33/week @ 2026-02-26 137/week @ 2026-03-05 342/week @ 2026-03-12 31/week @ 2026-03-19 388/week @ 2026-03-26 200/week @ 2026-04-02 135/week @ 2026-04-09

785 downloads per month

CC-BY-NC-SA-4.0

29KB
548 lines

Serac

A static, modular, and light-weight serialization framework.

Encoders specify what kind of serialization medium they can target, and define the serialization or deserialization process for that medium.

Serac comes with one built-in encoder Vanilla.

Why not serde?

Serac's value proposition is its ability to perform static analysis on serialization participants to either refine the failure space, or guarantee infallibility.

Examples

Serialize a number

use serac::{buf, SerializeIter};

let number = 0xdeadbeefu32;

let mut buf = buf!(u32); // [0; 4] in this case.
number.serialize_iter(&mut buf).unwrap();

let readback = SerializeIter::deserialize_iter(&buf).unwrap();
assert_eq(number, readback);

The "Vanilla" encoding is used by default unless otherwise specified.

In the above example, there was an unwrap used on the result of serialize_iter because the buffer could have been too short.

Using SerializeBuf, we can avoid this:

use serac::{buf, SerializeBuf};

let number = 0xdeadbeefu32;

let mut buf = buf!(u32);
number.serialize_buf(&mut buf); // it is statically known that "u32" fits in "[u8; 4]"

// it is *not* statically known that all values of "[u8; 4]" produce a valid "u32",
// and only that failure mode is expressed
let readback = SerializeBuf::deserialize_buf(&buf).unwrap();
assert_eq(number, readback);

Many built in types like numbers, tuples, and arrays implement both SerializeIter and SerializeBuf.

Serialize a custom type

use serac::{buf, encoding::vanilla, SerializeBuf};

const BE: u8 = 0xbe;

#[repr(u8)]
#[derive(Debug, PartialEq, vanilla::SerializeIter, vanilla::Size, SerializeBuf)]
enum Foo {
    A,
    B(u8, i16) = 0xde,
    C,
    D { bar: u16, t: i8 } = BE,
}

let foo = Foo::D { bar: 0xaa, t: -1 };

let mut buf = buf!(Foo);
foo.serialize_buf(&mut buf);

let readback = SerializeBuf::deserialize_buf(&buf).unwrap();
assert_eq(foo, readback);

This example shows a crazy enum with lots of fancy things going on, which is able to be serialized by serac.

Serialize a custom generic type

Mostly, the serialization of generic types is the same:

use serac::{buf, encoding::vanilla, SerializeIter, SerializeBuf};

const BE: u8 = 0xbe;

#[derive(Debug, PartialEq, vanilla::SerializeIter, vanilla::Size)]
#[repr(u16)]
enum Foo<T, U> {
    A(u8, T),
    B { woah: U } = BE as u16,
}

let foo = Foo::B { woah: 42i16 };

let mut buf = buf!(Foo<bool, i16>);
foo.serialize_iter(&mut buf).unwrap();

let readback = SerializeIter::deserialize_iter(&buf).unwrap();
assert_eq(foo, readback);

// ...

But SerializeBuf is not derivable on generic types.

You can, however, implement SerializeBuf for concretely specified aliases of generic types:

// ...

#[serac::serialize_buf]
type ConcreteFoo = Foo<bool, i16>;

let foo = Foo::B { woah: 42i16 };

let mut buf = buf!(ConcreteFoo);
foo.serialize_buf(&mut buf);

let readback = SerializeBuf::deserialize_buf(&buf).unwrap();
assert_eq(foo, readback);

Dependencies

~0.3–0.8MB
~16K SLoC