37 releases (breaking)
Uses new Rust 2024
| 0.32.0 | Apr 15, 2026 |
|---|---|
| 0.29.0 | Apr 1, 2026 |
| 0.28.0 | Mar 24, 2026 |
| 0.19.2 | Dec 28, 2025 |
| 0.4.0 | Oct 2, 2025 |
#539 in Algorithms
115KB
2K
SLoC
byteable
Byte-level serialization and deserialization for Rust types.
What is byteable?
byteable gives you two paths for working with binary data:
-
Fixed-size path — For types whose byte representation is known at compile time. Derive
Byteableand get zero-copyinto_byte_array()/from_byte_array()with a compile-timeBYTE_SIZEconstant. Under the hood this uses#[repr(C, packed)]raw structs andtransmute, so no allocation or iteration is involved. -
Dynamic path — For types that contain variable-length data (strings, vecs, maps). Add
#[byteable(io_only)]to deriveReadable/Writableinstead, which stream data through anystd::io::Read/Write(or their asynctokioequivalents).
Both paths share the same derive macro and attribute syntax, and both produce deterministic, self-describing wire formats.
Installation
[dependencies]
# default: derive macro + std I/O support
byteable = "0.31"
# with async (tokio) support
byteable = { version = "0.31", features = ["tokio"] }
# with ordered-float support
byteable = { version = "0.31", features = ["ordered-float"] }
# everything
byteable = { version = "0.31", features = ["all"] }
Quick Start
Fixed-size struct (zero-copy)
use byteable::{Byteable, IntoByteArray, TryFromByteArray};
#[derive(Byteable)]
struct Point3D {
x: f32,
y: f32,
z: f32,
}
let p = Point3D { x: 1.0, y: 2.0, z: 3.0 };
// Serialize to a fixed-size byte array — no allocation
let bytes: [u8; 12] = p.into_byte_array();
// Deserialize back
let p2 = Point3D::try_from_byte_array(bytes).unwrap();
assert_eq!(p.x, p2.x);
Dynamic struct (I/O streaming)
use byteable::{Byteable, Writable, Readable};
use byteable::io::{WriteValue, ReadValue};
#[derive(Byteable)]
#[byteable(io_only)]
struct Waypoint {
id: u32,
label: String,
tags: Vec<String>,
}
let wp = Waypoint {
id: 42,
label: "home".into(),
tags: vec!["start".into()],
};
let mut buf = Vec::new();
buf.write_value(&wp).unwrap();
let wp2 = std::io::Cursor::new(&buf).read_value::<Waypoint>().unwrap();
assert_eq!(wp.id, wp2.id);
assert_eq!(wp.label, wp2.label);
Controlling endianness
use byteable::Byteable;
#[derive(Byteable)]
#[byteable(big_endian)] // default for all fields
struct NetworkHeader {
#[byteable(big_endian)]
magic: u32,
#[byteable(little_endian)] // field-level override
payload_len: u16,
version: u8,
}
Feature Flags
| Feature | Default | Description |
|---|---|---|
derive |
yes | #[derive(Byteable)] proc-macro |
std |
yes | Readable / Writable I/O traits and std type impls |
tokio |
no | Async AsyncReadable / AsyncWritable via tokio |
ordered-float |
no | Impls for OrderedFloat<T> and NotNan<T> |
all |
no | Enable all of the above |
Wire Format Reference
| Type | Encoding |
|---|---|
u8, i8 |
1 byte, identity |
u16…u128, i16…i128 |
little-endian by default (overridable) |
f32, f64 |
little-endian IEEE 754 by default |
bool |
1 byte: 0 = false, 1 = true |
char |
4 bytes little-endian u32 (Unicode scalar value) |
NonZero<T> |
same as T; decoding rejects zero |
Option<T> |
1-byte tag (0 = None, 1 = Some) + optional value |
Result<V, E> |
1-byte tag (0 = Ok, 1 = Err) + payload |
String / str |
u64 byte length + UTF-8 bytes |
Vec<T> and other sequences |
u64 element count + elements |
HashMap<K,V> / BTreeMap<K,V> |
u64 entry count + alternating key/value pairs |
PathBuf / Path |
same as String; non-UTF-8 paths produce an error |
CString / CStr |
same as Vec<u8> (bytes without null terminator) |
Duration |
u64 secs + u32 nanos |
SystemTime |
i64 secs relative to Unix epoch + u32 nanos |
Ipv4Addr |
4 bytes (network octet order) |
Ipv6Addr |
16 bytes (network octet order) |
SocketAddrV4 |
Ipv4Addr + u16 port (LE) |
SocketAddrV6 |
Ipv6Addr + u16 port (LE) + u32 flowinfo (LE) + u32 scope_id (LE) |
Arc<T> / Rc<T> / Box<T> |
transparent passthrough to inner type |
[T; N] |
N consecutive encodings of T |
Range<T> / RangeInclusive<T> |
start + end |
RangeFrom<T> / RangeTo<T> / RangeToInclusive<T> |
single bound |
RangeFull |
0 bytes |
PhantomData<T> |
0 bytes |
Trait Reference
Fixed-size byte-array traits
These traits form the fixed-size serialization pipeline. #[derive(Byteable)] generates
impls for all of them automatically.
| Trait | Role |
|---|---|
IntoByteArray |
Serialize to a [u8; N]; provides the compile-time BYTE_SIZE constant |
FromByteArray |
Infallible deserialization from a [u8; N] |
TryFromByteArray |
Fallible deserialization from a [u8; N] (returns DecodeError) |
Raw representation traits
The "raw repr" layer sits between a typed value and its final bytes. It lets the derive macro insert endian wrappers transparently before transmuting.
| Trait | Role |
|---|---|
RawRepr |
Convert Self to a PlainOldData wire type (e.g. wrap a u32 in LittleEndian<u32>) |
FromRawRepr |
Infallible conversion from the raw type back to Self |
TryFromRawRepr |
Fallible conversion from the raw type back to Self |
I/O streaming traits (std feature)
These traits power the #[byteable(io_only)] derive path and the std collection impls.
| Trait | Role |
|---|---|
Readable |
Read a (possibly variable-length) value from any std::io::Read |
Writable |
Write a (possibly variable-length) value to any std::io::Write |
FixedReadable |
Read a fixed-size value; blanket impl for all TryFromRawRepr types |
FixedWritable |
Write a fixed-size value; blanket impl for all RawRepr types |
Extension traits that add ergonomic methods to any reader/writer:
| Trait | Added method | Works on |
|---|---|---|
ReadValue |
.read_value::<T>() |
any Read |
WriteValue |
.write_value(&val) |
any Write |
ReadFixed |
.read_fixed::<T>() |
any Read |
WriteFixed |
.write_fixed(&val) |
any Write |
Async I/O traits (tokio feature)
Async counterparts of the sync traits above, backed by tokio::io.
Endianness traits
These traits underpin per-field endian control in the derive macro and the
BigEndian<T> / LittleEndian<T> wrapper types.
| Trait / Type | Role |
|---|---|
EndianConvert |
Marker for multi-byte primitives that support byte-swapping (u16–u128, i16–i128, f32, f64) |
BigEndian<T> |
Transparent wrapper storing T in big-endian byte order |
LittleEndian<T> |
Transparent wrapper storing T in little-endian byte order |
HasEndianRepr |
Provides LE / BE associated types and to_little_endian() / to_big_endian() |
FromEndianRepr |
Infallible conversion back from an endian-specific repr |
TryFromEndianRepr |
Fallible conversion back from an endian-specific repr |
Low-level traits
| Trait | Role |
|---|---|
PlainOldData |
Unsafe marker: no padding, all bit patterns valid — enables transmute-based I/O |
ByteArray |
Unsafe marker for [u8; N] used as the IntoByteArray::ByteArray associated type |
Error types
| Type | When it occurs |
|---|---|
DecodeError |
Bytes decoded successfully but the value is invalid (bad discriminant, NaN, interior null, etc.) |
ReadableError |
An I/O error or DecodeError while reading from a Read / async reader |
License
MIT — see LICENSE.
Dependencies
~0–1.1MB
~20K SLoC