The Pure Rust successor to bincode just grew up into a full-featured codec.
Today we released OxiCode 0.2.0 — the first 0.2-series minor, and by far the largest API expansion since the debut. It keeps the same promise (a drop-in, 100%-bincode-2.0-binary-compatible encoder/decoder) and fills in the ergonomics that real production serialization demands: true zero-copy decoding, size pre-computation, file I/O, lazy streaming iterators, integrity checksums, and a deep set of derive attributes.
No C. No Fortran. No pickle, no Protobuf toolchain, no FFI.
Just memory-safe encode/decode that compiles to a single static binary (or WASM) and stays correct under Miri.
Why OxiCode 0.2.0 is a game changer
The 0.1.x line proved the thesis: you can replace bincode with pure Rust and gain SIMD, compression, and versioning without breaking the wire format. But day-to-day, three things kept coming up:
- Allocation control — you want to know the exact encoded size before you allocate.
- Zero-copy decode — you want to borrow out of the input buffer instead of cloning into owned types.
- Streaming and files — you want to read and write many records without hand-rolling framing.
0.2.0 closes all three gaps, and then adds a derive system rich enough that most custom encodings no longer need hand-written impls. All of it lands on top of unchanged, byte-for-byte bincode compatibility — your existing .bin files keep working.
Technical Deep Dive: the new surface area
The workspace shape is unchanged — oxicode (core), oxicode_derive (macros), and a compatibility crate — but the core layer is meaningfully wider:
-
Zero-copy decoding, first class
ABorrowDecodederive macro now generates borrowing impls for your own types, and the standard library coverage was extended:BorrowDecodeforBox<[T]>,Box<str>,Arc<[T]>,Arc<str>,Rc<[T]>,Rc<str>,String,char,Cow<str>andCow<[u8]>, non-zero integers, atomics,Wrapping<T>,Reverse<T>, andControlFlow. Decode straight out of the input slice without allocating. -
Size pre-computation
A newSizeWriterplus the top-levelencoded_size()/encoded_size_with_config()functions compute the exact byte length without allocating a buffer. Pair them withencode_to_vec_with_size_hint()to size yourVeconce and never reallocate — or useencode_to_fixed_array::<N>()to encode straight into a stack-allocated[u8; N]. -
Files, writers, and lazy streaming
encode_to_file()/decode_from_file()(under thestdfeature) andencode_to_writer()/decode_from_reader()cover the common I/O paths. For multi-record buffers,decode_iter_from_slice()returns a lazyDecodeIter<T>andencode_iter_to_vec()/encode_seq_to_vec()go the other way. ABufferedIoReader<R>wrapsBufReaderfor efficient streaming decode. -
Integrity and evolution
An optionalchecksumfeature adds CRC32 integrity checking (viacrc32fast), exposed throughencode_to_vec_checked/decode_from_slice_checked, and a newDecodeError::ChecksumMismatch. Versioned encoding gets ergonomic top-level helpers:encode_versioned_value/decode_versioned_value. -
A real derive vocabulary
Field attributes —skip,default,default_value,flatten,bytes,with,encode_with/decode_with,rename,seq_len. Container attributes —bound,rename_all,crate,transparent. Variant attributes —variant(custom discriminant),rename, and atag_typecontrol to pick the enum discriminant width (u8/u16/u32/u64). Hex display lands too, viaEncodedBytes/encode_to_hex/decode_from_hex.
Quality held the line: 19,929 tests passing with 0 regressions, 0 warnings, 0 clippy errors, 42 tests green under Miri (--no-default-features, zero undefined-behavior findings), cargo publish --dry-run clean for both crates, and every file under 2,000 lines.
Getting Started
cargo add oxicode
Round-trip a value, then borrow-decode and pre-compute its size — all 0.2.0 APIs:
use oxicode::{Encode, Decode, BorrowDecode};
#[derive(Encode, Decode, BorrowDecode, PartialEq, Debug)]
struct Frame<'a> {
id: u32,
label: &'a str,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let frame = Frame { id: 7, label: "telemetry" };
// Know the size before allocating
let n = oxicode::encoded_size(&frame)?;
// Encode (optionally size-hinted)
let bytes = oxicode::encode_to_vec_with_size_hint(&frame, n)?;
// Zero-copy decode: `label` borrows out of `bytes`
let (decoded, _): (Frame, _) = oxicode::borrow_decode_from_slice(&bytes)?;
assert_eq!(frame, decoded);
Ok(())
}
Already using serde? The optional serde feature gives you encode_serde / decode_serde convenience functions, now with i128/u128 support.
What’s New in 0.2.0
BorrowDecodederive macro plus broad zero-copy coverage for std types (Box/Arc/Rcslices & strings,Cow, atomics,Wrapping,Reverse,ControlFlow, non-zero ints)SizeWriterandencoded_size()/encoded_size_with_config()for allocation-free size pre-computationencode_to_file()/decode_from_file(),encode_to_writer()/decode_from_reader()(std)decode_iter_from_slice()→DecodeIter<T>, plusencode_iter_to_vec/encode_seq_to_vec/encode_seq_into_sliceand aBufferedIoReader<R>encode_to_fixed_array::<N>()for stack-allocated encoding;encode_to_vec_with_size_hintfor pre-sized buffers- CRC32 checksum feature with
encode_to_vec_checked/decode_from_slice_checkedandDecodeError::ChecksumMismatch - Versioned helpers
encode_versioned_value/decode_versioned_value - Rich derive attributes (
skip,default_value,flatten,with,encode_with/decode_with,rename,seq_len,bound,rename_all,transparent,tag_type, customvariantdiscriminants, …) - Hex display via
EncodedBytes/encode_to_hex/decode_from_hex - More std impls:
Ordering,Infallible,ControlFlow,OsStr/OsString,ManuallyDrop<T>, range types - Sharper diagnostics:
LimitExceedednow shows “limit: N, found: M”;Utf8Errorincludes the byte offset;UnexpectedVariantnames the type - Fixed: upgraded tokio to 1.50 to resolve RUSTSEC-2026-0007; Miri
format!inno_std; dropped unusedfutures-io - MSRV set to 1.70.0, aligned with the SciRS2 ecosystem; CI matrix across stable + MSRV on Ubuntu and macOS, with a Miri job and 4 cargo-fuzz targets
Tips
- Stop guessing buffer sizes. Call
encoded_size(&value)?and feed it toencode_to_vec_with_size_hintfor a single, exact allocation — or skip the heap entirely withencode_to_fixed_array::<N>(). - Borrow instead of clone. Add
BorrowDecodeto your derive and decode withborrow_decode_from_sliceto hand out&str/&[T]that point straight into the source buffer. - Stream many records cheaply.
decode_iter_from_slice()yields a lazyDecodeIter<T>, so you process a multi-item buffer without decoding it all up front. - Protect data at rest. Enable
features = ["checksum"]and useencode_to_vec_checked/decode_from_slice_checkedto catch corruption via CRC32 — aChecksumMismatchbeats a silent bad decode. - Tighten your enums. Use the
tag_typecontainer attribute to pick au8discriminant for small enums and shave bytes off every encoded variant. - Migrating live data? The wire format is still 100% bincode-2.0 compatible — adopt
config::legacy()where you need the classic fixed-int layout and mix readers during the transition.
This is the foundation
OxiCode is the binary codec the rest of the COOLJAPAN stack serializes against. With SciRS2 and NumRS2 for numerical checkpointing and data exchange, ToRSh for tensor buffers, OxiARC for archive-embedded payloads, and simulation engines like Spintronics for state save/load — 0.2.0’s zero-copy path and size-precise allocation make all of those hotter and leaner. Because it speaks serde and the bincode wire format, it drops into existing pipelines without a rewrite.
Repository: https://github.com/cool-japan/oxicode
Star the repo if you want fast, zero-copy, future-proof binary serialization without the old bincode limitations.
Pure Rust modern serialization is here — fast, compatible, and sovereign.
— KitaSan at COOLJAPAN OÜ March 16, 2026