This guide helps you migrate your project from bincode to OxiCode.
OxiCode is the successor to bincode, offering:
- Modern Rust practices (2021 edition)
- Strict safety guarantees (no-unwrap policy)
- Better error handling
- Active maintenance and long-term support
- Performance improvements
- Backward compatibility options
Replace in your Cargo.toml:
# Before
[dependencies]
bincode = "2.0"
# After
[dependencies]
oxicode = "0.2"// Before
use bincode::{Encode, Decode};
// After
use oxicode::{Encode, Decode};Most bincode functions have direct equivalents in OxiCode. Note that
bincode::serialize/bincode::deserialize are the bincode 1.x API — if
you are migrating from bincode = "1", that's the case that applies to you:
// Before (bincode 1.x — Cargo.toml: bincode = "1")
let encoded = bincode::serialize(&value)?;
let decoded: T = bincode::deserialize(&encoded)?;
// After (oxicode) — config::legacy() matches bincode 1.x's default wire format
let encoded = oxicode::encode_to_vec_with_config(&value, oxicode::config::legacy())?;
let (decoded, _len): (T, usize) = oxicode::decode_from_slice_with_config(&encoded, oxicode::config::legacy())?;If you are migrating from bincode 2.x (bincode = "2.0"), there is no
serialize/deserialize in that API at all — bincode 2 already uses
encode_to_vec/decode_from_slice with an explicit config, and the
migration is a near-identical rename:
// Before (bincode 2.x — Cargo.toml: bincode = "2.0")
use bincode::config;
let encoded = bincode::encode_to_vec(&value, config::standard())?;
let (decoded, _len): (T, usize) = bincode::decode_from_slice(&encoded, config::standard())?;
// After (oxicode) — same shape; the *_with_config suffix is oxicode's naming
let encoded = oxicode::encode_to_vec_with_config(&value, oxicode::config::standard())?;
let (decoded, _len): (T, usize) = oxicode::decode_from_slice_with_config(&encoded, oxicode::config::standard())?;// Before (bincode)
use bincode::config;
let config = config::standard();
let encoded = bincode::encode_to_vec(&value, config)?;
// After (oxicode)
let config = oxicode::config::standard();
let encoded = oxicode::encode_to_vec_with_config(&value, config)?;If you need exact bincode compatibility:
let config = oxicode::config::legacy(); // Wire-format compatible with bincode 1.x defaultOxiCode maintains similar feature flags to bincode:
[dependencies]
oxicode = { version = "0.2", features = ["derive"] }
# For no_std environments
oxicode = { version = "0.2", default-features = false, features = ["alloc"] }OxiCode's serde support is optional. If you're using serde types:
[dependencies]
oxicode = { version = "0.2", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }// Before (bincode)
use bincode::serde::{encode_to_vec, decode_from_slice};
let bytes = encode_to_vec(&value, config::standard())?;
let (decoded, _) = decode_from_slice(&bytes, config::standard())?;
// After (oxicode) - almost identical!
use oxicode::serde::{encode_to_vec, decode_from_slice};
let bytes = encode_to_vec(&value, oxicode::config::standard())?;
let (decoded, _) = decode_from_slice(&bytes, oxicode::config::standard())?;Important: like bincode 2.x (which also gates its serde integration behind
a serde Cargo feature), oxicode requires explicit features = ["serde"] in
Cargo.toml — this is not a divergence from bincode 2, only from bincode 1.x,
which bundled serde support unconditionally.
- Smaller binary size: Projects not using serde don't pay for it
- no_std compatibility: Serde-free usage in embedded environments
- Flexible: Use native
Encode/Decodetraits or serde, your choice
OxiCode uses its own error types:
// Before (bincode)
use bincode::error::DecodeError;
// After (oxicode)
use oxicode::Error;// Before (bincode)
fn process() -> Result<T, bincode::error::EncodeError> { ... }
// After (oxicode)
fn process() -> oxicode::Result<T> { ... }Reading data encoded with bincode does not require any extra dependency:
oxicode's own Decode/decode_from_slice (with a matching Config) reads
bincode-produced bytes directly (subject to the
Known compatibility caveats). There
is no runtime "compatibility layer" crate to add.
The repository does ship an internal oxicode_compatibility crate
(compatibility/), but it is publish = false and test-only — it exists to
assert, via cargo test, that oxicode's output is byte-identical to
bincode's for a battery of types and configs; it is never published to
crates.io and exports no runtime API. If you want to run that same
assertion suite against your own oxicode checkout:
git clone https://github.com/cool-japan/oxicode
cd oxicode
cargo test -p oxicode_compatibilityThis is a development-time check, not something your project depends on.
// Before
let bytes = bincode::serialize(&data)?;
// After
let bytes = oxicode::encode_to_vec_with_config(&data, oxicode::config::standard())?;// Before
let data: MyStruct = bincode::deserialize(&bytes)?;
// After
let (data, _len): (MyStruct, usize) = oxicode::decode_from_slice_with_config(&bytes, oxicode::config::standard())?;// Before
use bincode::{Encode, Decode};
#[derive(Encode, Decode)]
struct MyStruct {
field: String,
}
// After
use oxicode::{Encode, Decode};
#[derive(Encode, Decode)]
struct MyStruct {
field: String,
}OxiCode's default, oxicode::config::standard() (little-endian + varint), is
verified byte-identical to bincode 2.x's own config::standard() for the
types covered by the oxicode_compatibility test suite — you do not need to
change configs just to match bincode 2's default. If you're migrating from
bincode 1.x instead (which used fixed-width integers, not varint), match
that wire format with:
let config = oxicode::config::legacy();This ensures:
- Same fixed-int encoding
- Same byte ordering (little-endian)
- Wire-format compatible with bincode 1.x default (equivalent to bincode 2.0's
config::legacy()preset)
Both standard() and legacy() are subject to the same small list of
standard-library-type divergences — see
Known compatibility caveats in the README
(SystemTime, SocketAddrV6, IpAddr/SocketAddr/Bound<T> tag width,
Path/PathBuf, Ordering, Duration). These are known, tracked gaps
rather than a general "slightly different format" — everything else that the
compatibility suite exercises round-trips byte-for-byte.
- Keep both dependencies temporarily:
[dev-dependencies]
bincode = "2.0"
oxicode = "0.2"- Write compatibility tests:
#[test]
fn test_bincode_compatibility() {
let data = MyStruct { field: "test".into() };
// Encode with bincode (legacy mode = bincode 1.x wire format)
let bincode_bytes = bincode::encode_to_vec(&data, bincode::config::legacy())
.expect("bincode encode");
// Decode with oxicode using matching legacy config
let (decoded, _len): (MyStruct, usize) =
oxicode::decode_from_slice_with_config(&bincode_bytes, oxicode::config::legacy())
.expect("oxicode decode");
assert_eq!(data, decoded);
}OxiCode is designed to be as fast or faster than bincode:
- Run benchmarks before and after migration
- Use
cargo benchto compare performance - Report any performance regressions as issues
If you encounter issues during migration:
- Check the documentation
- Look at examples
- Open an issue on GitHub
We recommend:
- Week 1: Add oxicode as a dev-dependency and test
- Week 2-3: Gradually migrate code modules
- Week 4: Remove bincode dependency
- Ongoing: Monitor and optimize
If you need to rollback:
- Keep both dependencies during migration
- Use feature flags to switch between implementations
- Test thoroughly before removing bincode
[features]
default = ["use-oxicode"]
use-bincode = ["bincode"]
use-oxicode = ["oxicode"]OxiCode is committed to:
- Semantic versioning
- Long-term support (LTS) releases
- Clear migration paths for major versions
- Backward compatibility options
For questions or concerns about migration, please:
- Open a discussion on GitHub
- Check existing issues and PRs
- Reach out to the community
We're here to help make your migration smooth and successful!