Skip to main content

Crate bincode_next

Crate bincode_next 

Source
Expand description

Bincode-next is a crate for encoding and decoding using a tiny binary serialization strategy. Using it, you can easily go from having an object in memory, quickly serialize it to bytes, and then deserialize it back just as fast!

If you’re coming from bincode 1, check out our migration guide

§Serde

Starting from bincode 2, serde is now an optional dependency. If you want to use serde, please enable the serde feature. See Features for more information.

§Features

NameDefault?Affects MSRV?Supported types for Encode/DecodeEnabled methodsOther
stdYesNoHashMap and HashSetdecode_from_std_read and encode_into_std_write
allocYesNoAll common containers in alloc, like Vec, String, Boxencode_to_vec
deriveYesNoEnables the BorrowDecode, Decode, Encode, Fingerprint and BitPacked derive macros
serdeNoYes (MSRV reliant on serde)Compat and BorrowCompat, which will work for all types that implement serde’s traitsserde-specific encode/decode functions in the [serde] moduleNote: There are several known issues when using serde and bincode
zero-copyNoNoRelativePtr, ZeroArray, ZeroSlice, ZeroStr, ZeroStringEnables the relative_ptr module and the ZeroCopy derive macroZero-copy nested structures using offsets
static-sizeNoNoEnables the static_size module, the bounded module and the StaticSize derive macro
async-fiberNoNoEnables the async_fiber module and async decoding

§Which functions to use

Bincode-next has a couple of pairs of functions that are used in different situations.

SituationEncodeDecode
You’re working with [fs::File] or [net::TcpStream][encode_into_std_write][decode_from_std_read]
you’re working with in-memory buffers[encode_to_vec][decode_from_slice]
You want to use a custom Reader and Writer[encode_into_writer][decode_from_reader]
You’re working with pre-allocated buffers or on embedded targets[encode_into_slice][decode_from_slice]
You’re working with tokio-[decode_async_tokio_with_context][decode_async_tokio]
You’re working with futures-io-[decode_async_with_context][decode_async]

Note: If you’re using serde, use bincode_next::serde::... instead of bincode_next::...

§Getting Started

Add bincode-next to your Cargo.toml:

[dependencies]
bincode-next = "3.1.1"

§Basic Encode / Decode

let mut slice = [0u8; 100];

// You can encode any type that implements `Encode`.
// You can automatically implement this trait on custom types with the `derive` feature.
let input = (
    0u8,
    10u32,
    10000i128,
    'a',
    [0u8, 1u8, 2u8, 3u8]
);

let length = bincode_next::encode_into_slice(
    input,
    &mut slice,
    bincode_next::config::standard()
).unwrap();

let slice = &slice[..length];
println!("Bytes written: {:?}", slice);

// Decoding works the same as encoding.
// The trait used is `Decode`, and can also be automatically implemented with the `derive` feature.
let decoded: (u8, u32, i128, char, [u8; 4]) = bincode_next::decode_from_slice(slice, bincode_next::config::standard()).unwrap().0;

assert_eq!(decoded, input);
use bincode_next::Decode;
use bincode_next::Encode;
use bincode_next::config;

#[derive(Encode, Decode, PartialEq, Debug)]
struct Entity {
    x: f32,
    y: f32,
}

#[derive(Encode, Decode, PartialEq, Debug)]
struct World(Vec<Entity>);

fn main() {
    let config = config::standard();
    let world = World(vec![Entity { x: 0.0, y: 4.0 }, Entity { x: 10.0, y: 20.5 }]);

    let encoded: Vec<u8> = bincode_next::encode_to_vec(&world, config).unwrap();
    let (decoded, len): (World, usize) =
        bincode_next::decode_from_slice(&encoded[..], config).unwrap();

    assert_eq!(world, decoded);
    assert_eq!(len, encoded.len());
}

§Serde Compatibility

Bincode-Next works with any type that already derives serde::Serialize / serde::Deserialize — no need to re-derive Encode/Decode at all. Enable the serde feature and use the bincode_next::serde::* entry points.

[dependencies]
bincode-next = { version = "3.1.1", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
use serde::Deserialize;
use serde::Serialize;

// Only serde derives — no Encode/Decode needed.
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Config {
    host: String,
    port: u16,
    #[serde(default)]
    retries: u8,
}

fn main() {
    let cfg = Config {
        host: "localhost".into(),
        port: 8080,
        retries: 3,
    };

    // Encode via serde — honours all #[serde(...)] attributes
    let bytes =
        bincode_next::serde::encode_to_vec(&cfg, bincode_next::config::standard()).unwrap();

    let (decoded, _): (Config, usize) =
        bincode_next::serde::decode_from_slice(&bytes, bincode_next::config::standard())
            .unwrap();
    assert_eq!(cfg, decoded);
}

You can also mix: derive both Serialize and Encode on the same type, then use #[bincode(with_serde)] on individual fields to route specific fields through their serde impl (useful for types that only implement Serialize, not Encode).


§Bit-Packing

Enable bit-packing in your configuration to pack fields at bit granularity. Consecutive #[bincode(bits = N)] fields share bytes — 3 bits + 5 bits = exactly 1 byte on the wire.

use bincode_next::BitPacked;
use bincode_next::config;

#[derive(BitPacked, PartialEq, Debug)]
struct Telemetry {
    #[bincode(bits = 1)]
    is_active: bool,
    #[bincode(bits = 1)]
    has_error: bool,
    #[bincode(bits = 3)]
    mode: u8,
    // ↑ 5 bits total → 1 byte on the wire when bit-packing is enabled
}

fn main() {
    let config = config::standard().with_bit_packing();
    let t = Telemetry {
        is_active: true,
        has_error: false,
        mode: 5,
    };

    let encoded = bincode_next::encode_to_vec(&t, config).unwrap();
    assert_eq!(encoded.len(), 1); // 5 bits packed into 1 byte

    let (decoded, _): (Telemetry, usize) =
        bincode_next::decode_from_slice(&encoded, config).unwrap();
    assert_eq!(decoded, t);
}

§Zero-Copy Structures

The zero-copy feature lets you build flat byte blobs that can be accessed as typed Rust references without any deserialization step — ideal for memory-mapped files, shared memory, and IPC.

#[derive(ZeroCopy)] on a #[repr(C, u8)] enum generates a companion *Builder type that mirrors every variant. Use ZeroBuilder to accumulate bytes, reserve::<T>() to claim space, and build_to_target() to write and get back a live typed reference directly into the buffer.

#[cfg(all(feature = "zero-copy", feature = "alloc"))]
use bincode_next::DeepValidator;
#[cfg(all(feature = "zero-copy", feature = "alloc"))]
use bincode_next::ZeroBuilder;
#[cfg(all(feature = "zero-copy", feature = "alloc"))]
use bincode_next::ZeroCopyBuilder;

/// Packet layout stored verbatim in the byte blob.
#[derive(bincode_derive_next::ZeroCopy, Debug, PartialEq, Eq)]
#[repr(C, u8)]
enum Packet {
    Ping,
    Data { seq: u32, value: u64 },
    Error(u32),
}

#[cfg(all(feature = "zero-copy", feature = "alloc"))]
fn main() {
    let mut builder = ZeroBuilder::new();

    // — Ping ----------------------------------------------------------------
    let ping_offset = builder.reserve::<Packet>();
    let ping_view = PacketBuilder::Ping.build_to_target(&mut builder, ping_offset);
    assert_eq!(ping_view, Packet::Ping);

    // — Data ----------------------------------------------------------------
    let data_offset = builder.reserve::<Packet>();
    let data_view = PacketBuilder::Data {
        seq: 7,
        value: 0xDEAD_BEEF,
    }
    .build_to_target(&mut builder, data_offset);

    match data_view {
        | Packet::Data { seq, value } => {
            assert_eq!(seq, 7);
            assert_eq!(value, 0xDEAD_BEEF);
        },
        | _ => unreachable!(),
    }

    // — Error ---------------------------------------------------------------
    let err_offset = builder.reserve::<Packet>();
    let err_view = PacketBuilder::Error(404).build_to_target(&mut builder, err_offset);

    match err_view {
        | Packet::Error(code) => assert_eq!(code, 404),
        | _ => unreachable!(),
    }

    // All three packets live in one contiguous allocation — no heap per variant.
    let _bytes = builder.finish();
}

For lower-level use, RelativePtr<T, OFFSET_SIZE> lets you embed self-relative pointers inside any #[repr(C)] struct:

#[cfg(feature = "zero-copy")]
use bincode_next::DeepValidator;
#[cfg(feature = "zero-copy")]
use bincode_next::RelativePtr;

#[repr(align(8))]
struct AlignedBuf<const N: usize>(pub [u8; N]);

#[cfg(feature = "zero-copy")]
fn relative_ptr_example() {
    let mut buf = AlignedBuf([0u8; 12]);
    let b = &mut buf.0;

    b[0..4].copy_from_slice(&8i32.to_ne_bytes()); // 4-byte signed offset stored at position 0
    b[8..12].copy_from_slice(&42u32.to_ne_bytes()); // target value at position 8

    let ptr = unsafe { &*(b.as_ptr() as *const RelativePtr<u32, 4>) };
    // is_valid_deep also validates any nested relative pointers recursively
    assert!(ptr.is_valid_deep(b));
    assert_eq!(*ptr.get(b).unwrap(), 42);
}

§Compile-time Memory Bounds (StaticSize)

StaticSize gives a compile-time upper bound on encoded size — useful for stack allocation and no_std fixed-size buffers. Enable with the static-size feature.

MAX_SIZE assumes worst-case varint encoding; PACKED_MAX_SIZE is tighter when bit-packing is active (consecutive #[bincode(bits = N)] fields share bytes).

#[cfg(feature = "static-size")]
use bincode_next::BitPacked;
#[cfg(feature = "static-size")]
use bincode_next::StaticSize;

#[cfg(feature = "static-size")]
#[derive(bincode_next::Encode, bincode_next::Decode, StaticSize, PartialEq, Debug)]
struct Packet {
    seq: u32,  // varint: up to 5 bytes
    data: u64, // varint: up to 9 bytes
}

#[cfg(feature = "static-size")]
#[derive(BitPacked, StaticSize, PartialEq, Debug)]
struct Flags {
    #[bincode(bits = 4)]
    kind: u8,
    #[bincode(bits = 4)]
    priority: u8,
}

#[cfg(feature = "static-size")]
fn main() {
    // Packet: 5 (u32) + 9 (u64) = 14 bytes worst-case
    assert_eq!(Packet::MAX_SIZE, 14);

    // Flags without packing: two full u8s = 2 bytes
    assert_eq!(Flags::MAX_SIZE, 2);
    // Flags with packing: 4+4 bits = 1 byte
    assert_eq!(Flags::PACKED_MAX_SIZE, 1);

    // Use MAX_SIZE for a guaranteed-large-enough stack buffer
    let val = Packet { seq: 1, data: 42 };
    let mut buf = [0u8; Packet::MAX_SIZE];
    let _ = bincode_next::encode_into_slice(&val, &mut buf, bincode_next::config::standard())
        .unwrap();

    // decode_from_slice_static takes &[u8; N] — pass the whole fixed-size array
    let decoded: Packet =
        bincode_next::decode_from_slice_static(&buf, bincode_next::config::standard()).unwrap();
    assert_eq!(val, decoded);
}

§Schema Fingerprinting

Fingerprinting embeds a 64-bit schema hash into each encoded message. The hash covers field names, types, ordering, and the full configuration — including format (Bincode vs CBOR), endianness, integer encoding, and all CBOR options. Any mismatch between encoder and decoder returns a DecodeError::SchemaHashMismatch.

use bincode_next::Decode;
use bincode_next::Encode;
use bincode_next::Fingerprint;
use bincode_next::config;

#[derive(Encode, Decode, Fingerprint, PartialEq, Debug, Clone)]
struct PlayerV1 {
    id: u32,
    score: u64,
}

// Adding a field changes the schema hash → decode_from_slice returns an error
#[derive(Encode, Decode, Fingerprint, PartialEq, Debug, Clone)]
struct PlayerV2 {
    id: u32,
    score: u64,
    level: u32, // new field
}

fn main() {
    let config = config::standard().with_fingerprint();
    let player = PlayerV1 { id: 1, score: 9001 };

    let encoded = bincode_next::encode_to_vec(&player, config).unwrap();

    // Decoding as V1 succeeds
    let (decoded, _): (PlayerV1, usize) =
        bincode_next::decode_from_slice(&encoded, config).unwrap();
    assert_eq!(decoded, player);

    // Decoding as V2 fails — schema hashes differ
    let result = bincode_next::decode_from_slice::<PlayerV2, _>(&encoded, config);
    assert!(result.is_err());

    // Switching formats also changes the hash; cross-format decoding is caught too
    let cbor_config = config::standard().with_fingerprint().with_cbor_format();
    let result = bincode_next::decode_from_slice::<PlayerV1, _>(&encoded, cbor_config);
    assert!(result.is_err());
}

§CBOR Format

Bincode-Next implements full RFC 8949 CBOR encoding. Switch formats with a single config call; all existing derives work unchanged.

use bincode_next::Decode;
use bincode_next::Encode;
use bincode_next::config;

#[derive(Encode, Decode, PartialEq, Debug)]
struct Event {
    timestamp: u64,
    value: f32,
}

fn main() {
    let config = config::standard().with_cbor_format();
    let event = Event {
        timestamp: 1_700_000_000,
        value: 3.14,
    };

    let encoded = bincode_next::encode_to_vec(&event, config).unwrap();
    let (decoded, _): (Event, usize) =
        bincode_next::decode_from_slice(&encoded, config).unwrap();
    assert_eq!(event, decoded);

    // Deterministic (canonical) CBOR for hashing or signing
    let det_config = config::standard().with_deterministic_cbor();
    let det_encoded = bincode_next::encode_to_vec(&event, det_config).unwrap();
    let (det_decoded, _): (Event, usize) =
        bincode_next::decode_from_slice(&det_encoded, det_config).unwrap();
    assert_eq!(event, det_decoded);
}

§Async Fiber Decoding

Bincode-Next supports true zero-cost asynchronous decoding using Unified Fiber-backed Async (UFA). Synchronous Decode traits run on a dedicated lightweight fiber stack, avoiding state-machine code generation overhead entirely.

use bincode_next::Decode;
use bincode_next::Encode;
use bincode_next::config;
use bincode_next::decode_async;
use bincode_next::encode_to_vec;

#[derive(Encode, Decode, PartialEq, Debug)]
struct Entity {
    x: f32,
    y: f32,
}

#[tokio::main]
#[cfg_attr(miri, ignore)]
async fn main() {
    if cfg!(miri) {
        return;
    }

    let entity = Entity { x: 1.0, y: 2.0 };
    let encoded = encode_to_vec(&entity, config::standard()).unwrap();

    // Any type implementing `futures_io::AsyncRead` works here.
    let mut reader: &[u8] = &encoded;
    let decoded: Entity = decode_async(config::standard(), &mut reader).await.unwrap();
    assert_eq!(entity, decoded);
}

Re-exports§

pub use de::BorrowDecode;
pub use de::Decode;
pub use enc::Encode;
pub use fingerprint::Fingerprint;

Modules§

boundedalloc and static-size and (alloc or derive or serde or static-size or std or zero-copy)
Bounded types for compile-time size guarantees.
config
The config module is used to change the behavior of bincode’s encoding and decoding logic.
de
Decoder-based structs and traits.
enc
Encoder-based structs and traits.
error
Errors that can be encountering by Encoding and Decoding.
fingerprint
Fingerprinting support for schema verification. Fingerprinting implementation for bincode.
migration_guide
Migrating from bincode 1 to 2
serdeserde
Support for serde integration. Enable this with the serde feature.
specalloc and derive
Serialization Specification
static_sizestatic-size and (alloc or derive or serde or static-size or std or zero-copy)
Static size trait for compile-time memory bound validation.
zero_copyzero-copy
Relative pointer system for zero-copy nested structures

Structs§

IoReaderstd and (alloc or derive or serde or static-size or std or zero-copy)
A reader that reads from a std::io::Read.
IoWriterstd and (alloc or derive or serde or static-size or std or zero-copy)
A writer that writes to a std::io::Write.
VecWriteralloc and (alloc or derive or serde or static-size or std or zero-copy)
A writer that writes into a Vec<u8>.

Constants§

BINCODE_MAJOR_VERSION
The major version of the bincode library.

Traits§

StaticSizestatic-size
A trait that calculates the upper bound of a type’s serialized size as a const value.
StaticSizezero-copy
Indicates that a type has a fixed size known at compile time. This allows us to perform bounds checking efficiently.

Functions§

borrow_decode_from_slice
Attempt to decode a given type D from the given slice. Returns the decoded output and the amount of bytes read.
borrow_decode_from_slice_staticstatic-size
Attempt to decode a given type D from the given slice with a compile-time bound check.
borrow_decode_from_slice_static_with_contextstatic-size
Attempt to borrow-decode a given type D from the given slice with a compile-time bound check and a decoding context.
borrow_decode_from_slice_with_context
Attempt to decode a given type D from the given slice with Context. Returns the decoded output and the amount of bytes read.
decode_asyncasync-fiber
Attempt to decode a given type T from the given async reader safely using a non-blocking fiber.
decode_async_tokioasync-fiber and tokio
Attempt to decode a given type T from the given tokio async reader safely using a non-blocking fiber.
decode_async_tokio_with_contextasync-fiber and tokio
Attempt to decode a given type T from the given tokio async reader using a non-blocking fiber and a context.
decode_async_with_contextasync-fiber
Attempt to decode a given type T from the given async reader using a non-blocking fiber and a context.
decode_from_reader
Attempt to decode a given type D from the given [Reader].
decode_from_slice
Attempt to decode a given type D from the given slice. Returns the decoded output and the amount of bytes read.
decode_from_slice_staticstatic-size
Attempt to decode a given type D from the given slice with a compile-time bound check.
decode_from_slice_static_with_contextstatic-size
Attempt to decode a given type D from the given slice with a compile-time bound check and a decoding context.
decode_from_slice_with_context
Attempt to decode a given type D from the given slice with Context. Returns the decoded output and the amount of bytes read.
decode_from_std_readstd
Decode type D from the given reader with the given Config. The reader can be any type that implements std::io::Read, e.g. std::fs::File.
decode_from_std_read_with_contextstd
Decode type D from the given reader with the given Config and Context. The reader can be any type that implements std::io::Read, e.g. std::fs::File.
encode_into_slice
Encode the given value into the given slice. Returns the amount of bytes that have been written.
encode_into_std_writestd
Encode the given value into any type that implements std::io::Write, e.g. std::fs::File, with the given Config.
encode_into_writer
Encode the given value into a custom [Writer].
encode_to_vecalloc
Encode the given value into a Vec<u8> with the given Config. See the config module for more information.

Derive Macros§

BitPackedderive
BorrowDecodederive
Decodederive
Encodederive
Fingerprintderive
StaticSizederive and static-size
ZeroCopyderive and zero-copy