bitcram
A small, derive-based bit packing library for Rust. Pack structured data into compact integer buffers with sub-byte field granularity.
Features
#[packable(B)]attribute macro for automaticPackableimplementations- Buffer types:
u8,u16,u32,u64,u128 - Supports structs (unit, tuple, named) and enums with mixed variant kinds
- Generics with type parameters (bounds auto-applied)
- Debug-mode safety checks with zero release-build overhead
- Performance comparable to established bit-packing crates
Quick Start
Encode a mini-chess move (5×6 board, 6 piece types, with promotion) into 16 bits:
use ;
/// A coordinate on a 5×6 board.
///
/// 30 distinct (x, y) pairs fit in 5 bits. A naive "3 bits for x + 3 bits for y"
/// layout would use 6 bits — implementing `Packable` directly lets us collapse
/// the two fields into a single 5-bit index and reclaim that bit.
// 6 variants → 3 bits
// 5 variants → 3 bits
// 16 bits total — fits exactly in u16
let m = Move ;
let packed: u16 = m.pack; // 2 bytes
assert_eq!;
Each move serializes to 2 bytes. A naive byte-aligned encoding of the same data would take 4 bytes or more.
Supported types
#[packable(B)] can derive Packable<B> for:
- Unit structs (
struct Foo;) —SIZE = 0 - Tuple structs (
struct Foo(X, Y);) - Named structs (
struct Foo { x: X, y: Y }) - Enums with any combination of variant kinds:
- Unit variants (
Bar) - Empty tuple variants (
Bar()) - Tuple variants (
Bar(X, Y)) - Named variants (
Bar { x: X, y: Y })
- Unit variants (
- Generic types —
Packable<B>bounds are auto-applied to type parameters
Multiple buffer types can be specified to generate one impl per type:
#[packable(u16, u32, u64)]
struct Foo { #[bits(5)] x: u8, #[bits(5)] y: u8 }
Built-in Packable implementations are provided for:
| Type | SIZE |
|---|---|
bool |
1 |
Option<T> |
1 + T::SIZE |
(T1, T2, ..., Tn) (up to 12-tuples) |
T1::SIZE + T2::SIZE + ... + Tn::SIZE |
[T; N] |
N * T::SIZE |
For primitive types or custom encodings (like Coord above), implement Packable<B> manually — or annotate fields with #[bits(N)] to specify the bit width directly:
use ;
let foo = Foo ;
assert_eq!;
Without #[bits], the field type must implement Packable<B> — there is no automatic width inference from primitive types. This makes the requirement explicit at the cost of a small annotation. The field type must be cast-compatible with the buffer type in both directions; all primitive unsigned integers (u8, u16, u32, u64, u128) qualify.
The field type must be cast-compatible with the buffer type in both directions (*field as B and B as FieldType). All primitive unsigned integers (u8, u16, u32, u64, u128) qualify.
Conventions
The library trades runtime checks for performance, with a few rules the caller is expected to follow:
Packable::pack()must return a value that fits inSIZEbits. Debug builds verify this withassert!; release builds silently mask oversized values to prevent buffer corruption.- Total packed size must not exceed
B::BITS. Debug builds track cumulative bits; release builds rely on this convention. - A single
raw_pack/raw_unpackcall must usesize < B::BITS. Compose multiple smaller calls if you need to fill the buffer exactly. (Shifting by the full type width is undefined behavior in Rust.) - Empty enums are silently skipped by the derive macro. This allows incremental development without compile errors on placeholder types.
- The
Unpackertrusts its input. Decoding malformed data may produce garbage but will not panic on its own.
Benchmarks
Compared against similar crates packing 4 fields (4-bit + 4-bit + 8-bit + 16-bit = 32 bits total):
| Crate | Pack | Unpack | Round-trip | Output size |
|---|---|---|---|---|
| bitcram | 934 ps | 1.61 ns | 2.00 ns | 4 bytes |
| modular-bitfield | 967 ps | 1.54 ns | 1.99 ns | 4 bytes |
| bitfield-struct | 970 ps | 1.55 ns | 2.00 ns | 4 bytes |
| bincode 2.x | 3.82 ns | 4.33 ns | 8.09 ns | ~6 bytes |
| postcard | 6.32 ns | 2.98 ns | 9.16 ns | ~6 bytes |
The three bit-packers are essentially equivalent. Byte-level serializers are ~4× slower on round-trip and produce ~50% larger output for this kind of small, fixed-shape data.
Numbers were measured on a single machine and will vary by hardware; reproduce locally with:
Workspace structure
bitcram/— runtime crate (Packable,Buffer,Packer,Unpacker)bitcram_derive/— proc-macro crate (#[packable])bitcram_bench/— comparison benchmarks (not published)
Requirements
- Rust 1.85+ (edition 2024)
License
Licensed under either of MIT or Apache License 2.0 at your option.