reliakit-codec 0.2.1

Deterministic canonical binary encoding and decoding traits for reliability-oriented Rust values.
Documentation

reliakit-codec

Crates.io Crates.io Downloads Docs.rs CI codecov License: MIT

Deterministic canonical binary encoding and decoding traits for Rust.

reliakit-codec is a small codec crate for reliability-oriented Rust code. It defines one canonical binary representation for each supported type and keeps encoding explicit through handwritten trait implementations.

The crate has no external dependencies by default, supports no_std with alloc, and forbids unsafe code.

Introduction

Binary codecs are often used at boundaries where ambiguity is expensive: protocol messages, fixtures, cache keys, deterministic tests, and small storage formats. This crate focuses on those use cases.

It does not try to infer schemas, derive implementations, or support every Rust type. Instead, it provides simple traits, strict decode behavior, primitive implementations, and helpers for exact slice decoding.

What This Crate Does

This crate provides:

  • CanonicalEncode for writing values in a deterministic binary format,
  • CanonicalDecode for strict decoding,
  • small sink/source traits that work without std::io,
  • helpers such as encode_to_vec and decode_from_slice_exact,
  • canonical implementations for integers, bool, strings, vectors, options, results, fixed arrays, and small tuples,
  • optional integrations with reliakit-primitives.

When To Use

Use this crate when you want a compact binary representation that is explicit and deterministic:

  • handwritten protocol messages,
  • golden test fixtures,
  • stable cache keys,
  • small local persistence formats,
  • library boundaries where canonical encoding matters.

When Not To Use

Use a different tool when you need automatic derive support, schema negotiation, multi-format serialization, RPC abstractions, or broad ecosystem interoperability.

reliakit-codec is intentionally small. It focuses on explicit canonical binary encoding, strict decoding, and deterministic byte output.

HashMap and other unordered maps are not supported because their iteration order is not canonical. Floats are not supported because NaN payloads, signed zero, and normalization rules need an explicit design before they can be reliably canonical. usize and isize are not supported because their width is platform-dependent.

Installation

[dependencies]
reliakit-codec = "0.2"

With optional reliakit-primitives integrations:

[dependencies]
reliakit-codec = { version = "0.2", features = ["primitives"] }

For no_std with allocation:

[dependencies]
reliakit-codec = { version = "0.2", default-features = false, features = ["alloc"] }

Core Concepts

Canonical Encoding

Every supported type has one valid byte representation. Integers use fixed-width little-endian encoding. Booleans use exactly 0x00 or 0x01. Length-prefixed types use a u32 little-endian length.

Explicit Decode

Decode is performed through CanonicalDecode. Structs and enums implement the trait manually so field order, enum tags, and validation rules are visible in normal Rust code.

Strict Errors

Invalid bytes fail. Invalid UTF-8 fails. Unknown enum tags fail. Exact decoding fails when trailing bytes remain.

CodecErrorKind provides stable categories for programmatic handling, while CodecError::message() gives a concise explanation.

Manual Trait Implementations

There are no derive macros in this version.

use reliakit_codec::{CanonicalDecode, CanonicalEncode, CodecError, DecodeSource, EncodeSink};

struct Point {
    x: u16,
    y: u16,
}

impl CanonicalEncode for Point {
    fn encode<W: EncodeSink + ?Sized>(&self, writer: &mut W) -> Result<(), CodecError> {
        self.x.encode(writer)?;
        self.y.encode(writer)
    }
}

impl CanonicalDecode for Point {
    fn decode<R: DecodeSource + ?Sized>(reader: &mut R) -> Result<Self, CodecError> {
        Ok(Self {
            x: u16::decode(reader)?,
            y: u16::decode(reader)?,
        })
    }
}

Binary Format Overview

Type Encoding
u8, i8 one byte
u16, i16 fixed-width little-endian
u32, i32 fixed-width little-endian
u64, i64 fixed-width little-endian
u128, i128 fixed-width little-endian
bool 0x00 for false, 0x01 for true
str, String u32 little-endian byte length, then UTF-8 bytes
Vec<T> u32 little-endian item count, then each item
Option<T> 0x00 for None, 0x01 then value for Some
Result<T, E> 0x00 then value for Ok, 0x01 then error for Err
[T; N] items in order
tuples up to arity 4 fields in order

Any other bool, Option, or Result tag is invalid. Enum tags in user-defined types should follow the same strict pattern.

Generic [T; N] decoding requires the alloc feature in this version because the crate forbids unsafe code and Rust 1.85 does not provide a stable fallible array initializer. In no-alloc builds, [u8; N] decoding is supported directly.

Examples

Encode and decode a primitive:

use reliakit_codec::{decode_from_slice_exact, encode_to_vec};

let encoded = encode_to_vec(&8080u16)?;
assert_eq!(encoded, [0x90, 0x1f]);
assert_eq!(decode_from_slice_exact::<u16>(&encoded)?, 8080);
# Ok::<(), reliakit_codec::CodecError>(())

Reject trailing bytes:

use reliakit_codec::{decode_from_slice_exact, CodecErrorKind};

let err = decode_from_slice_exact::<u8>(&[1, 2]).unwrap_err();
assert_eq!(err.kind(), CodecErrorKind::TrailingBytes);

See examples/basic_encoding.rs and examples/protocol_message.rs for complete examples.

Feature Flags

Flag Default Description
std yes Enables std::error::Error for CodecError and enables alloc
alloc no Enables allocation-backed helpers and types such as String and Vec<T>
primitives no Adds optional implementations for supported reliakit-primitives types

The primitives feature is optional and is not enabled by default.

no_std Support

The crate supports no_std when default features are disabled.

Use features = ["alloc"] when you need String, Vec<T>, arrays decoded via temporary vectors, or encode_to_vec.

Without alloc, the core traits, byte-slice reader, integer implementations, bool, Option, Result, tuple implementations, array encoding, and [u8; N] decoding remain available.

Safety

This crate uses #![forbid(unsafe_code)].

Decode is strict by default:

  • invalid bool bytes fail,
  • unknown tags should fail in manual enum implementations,
  • invalid UTF-8 fails,
  • exact slice decoding rejects trailing bytes,
  • length prefixes are checked before conversion to usize.

Allocation-backed decoders allocate according to canonical u32 length prefixes. Applications with tighter memory limits should validate framing before calling into allocation-backed decoders.

MSRV

The minimum supported Rust version is Rust 1.85.

Status

This is the initial 0.1.0 release. It focuses on clear traits, documented format rules, primitive implementations, examples, tests, and optional integration with reliakit-primitives.

Future features may add more integrations, such as a separate reliakit-collections feature for collection-specific types. Derive macros, schema negotiation, floats, pointer-sized integers, and unordered maps are intentionally out of scope for this version.