Expand description

Serialization specification

NOTE: Serialization is done by bincode_derive by default. If you enable the serde flag, serialization with serde-derive is supported as well. serde-derive has the same guarantees as bincode_derive for now.

Related issue: https://github.com/serde-rs/serde/issues/1756#issuecomment-689682123

Endian

By default bincode will serialize values in little endian encoding. This can be overwritten in the Config.

Basic types

Boolean types are encoded with 1 byte for each boolean type, with 0 being false, 1 being true. Whilst deserializing every other value will throw an error.

All basic numeric types will be encoded based on the configured IntEncoding.

All floating point types will take up exactly 4 (for f32) or 8 (for f64) bytes.

All tuples have no additional bytes, and are encoded in their specified order, e.g.

let tuple = (u32::min_value(), i32::max_value()); // 8 bytes
let encoded = bincode::encode_to_vec(tuple, bincode::config::legacy()).unwrap();
assert_eq!(encoded.as_slice(), &[
    0,   0,   0,   0,  // 4 bytes for first type:  u32
    255, 255, 255, 127 // 4 bytes for second type: i32
]);

IntEncoding

Bincode currently supports 2 different types of IntEncoding. With the default config, VarintEncoding is selected.

VarintEncoding

Encoding an unsigned integer v (of any type excepting u8/i8) works as follows:

  1. If u < 251, encode it as a single byte with that value.
  2. If 251 <= u < 2**16, encode it as a literal byte 251, followed by a u16 with value u.
  3. If 2**16 <= u < 2**32, encode it as a literal byte 252, followed by a u32 with value u.
  4. If 2**32 <= u < 2**64, encode it as a literal byte 253, followed by a u64 with value u.
  5. If 2**64 <= u < 2**128, encode it as a literal byte 254, followed by a u128 with value u.

usize is being encoded/decoded as a u64 and isize is being encoded/decoded as a i64.

See the documentation of VarintEncoding for more information.

FixintEncoding

  • Fixed size integers are encoded directly
  • Enum discriminants are encoded as u32
  • Lengths and usize are encoded as u64

See the documentation of FixintEncoding for more information.

Enums

Enums are encoded with their variant first, followed by optionally the variant fields. The variant index is based on the IntEncoding during serialization.

Both named and unnamed fields are serialized with their values only, and therefor encode to the same value.

#[derive(bincode::Encode)]
pub enum SomeEnum {
    A,
    B(u32),
    C { value: u32 },
}

// SomeEnum::A
let encoded = bincode::encode_to_vec(SomeEnum::A, bincode::config::legacy()).unwrap();
assert_eq!(encoded.as_slice(), &[
    0, 0, 0, 0, // first variant, A
    // no extra bytes because A has no fields
]);

// SomeEnum::B(0)
let encoded = bincode::encode_to_vec(SomeEnum::B(0), bincode::config::legacy()).unwrap();
assert_eq!(encoded.as_slice(), &[
    1, 0, 0, 0, // first variant, B
    0, 0, 0, 0  // B has 1 unnamed field, which is an u32, so 4 bytes
]);

// SomeEnum::C { value: 0u32 }
let encoded = bincode::encode_to_vec(SomeEnum::C { value: 0u32 }, bincode::config::legacy()).unwrap();
assert_eq!(encoded.as_slice(), &[
    2, 0, 0, 0, // first variant, C
    0, 0, 0, 0  // C has 1 named field which is a u32, so 4 bytes
]);

Collections

Collections are encoded with their length value first, following by each entry of the collection. The length value is based on your IntEncoding.

note: fixed array length may not have their len encoded. See Arrays

let list = vec![
    0u8,
    1u8,
    2u8
];

let encoded = bincode::encode_to_vec(list, bincode::config::legacy()).unwrap();
assert_eq!(encoded.as_slice(), &[
    3, 0, 0, 0, 0, 0, 0, 0, // length of 3u64
    0, // entry 0
    1, // entry 1
    2, // entry 2
]);

This also applies to e.g. HashMap, where each entry is a tuple of the key and value.

String and &str

Both String and &str are treated as a Vec<u8>. See Collections for more information.

let str = "Hello"; // Could also be `String::new(...)`

let encoded = bincode::encode_to_vec(str, bincode::config::legacy()).unwrap();
assert_eq!(encoded.as_slice(), &[
    5, 0, 0, 0, 0, 0, 0, 0, // length of the string, 5 bytes
    b'H', b'e', b'l', b'l', b'o'
]);

Arrays

Array length is encoded based on the .write_fixed_array_length and .skip_fixed_array_length() config. When an array length is written, it will be encoded as a u64.

Note that &[T] is encoded as a Collection.

let arr: [u8; 5] = [10, 20, 30, 40, 50];
let encoded = bincode::encode_to_vec(arr, bincode::config::legacy()).unwrap();
assert_eq!(encoded.as_slice(), &[
    5, 0, 0, 0, 0, 0, 0, 0, // The length, as a u64
    10, 20, 30, 40, 50, // the bytes
]);

let encoded = bincode::encode_to_vec(arr, bincode::config::legacy().skip_fixed_array_length()).unwrap();
assert_eq!(encoded.as_slice(), &[
    // no length
    10, 20, 30, 40, 50, // the bytes
]);

This applies to any type T that implements Encode/Decode

#[derive(bincode::Encode)]
struct Foo {
    first: u8,
    second: u8
};

let arr: [Foo; 2] = [
    Foo {
        first: 10,
        second: 20,
    },
    Foo {
        first: 30,
        second: 40,
    },
];

let encoded = bincode::encode_to_vec(&arr, bincode::config::legacy()).unwrap();
assert_eq!(encoded.as_slice(), &[
    2, 0, 0, 0, 0, 0, 0, 0, // Length of the array
    10, 20, // First Foo
    30, 40, // Second Foo
]);

let encoded = bincode::encode_to_vec(&arr, bincode::config::legacy().skip_fixed_array_length()).unwrap();
assert_eq!(encoded.as_slice(), &[
    // no length
    10, 20, // First Foo
    30, 40, // Second Foo
]);