peteris.rocks

Serialize any object to a binary format in Rust

How to serialize any object to a binary format in Rust using rustc-serialize and bincode

Last updated on

Serialization

Add rustc-serialize to your Cargo.toml file:

[dependencies]
rustc-serialize = "*"

Then annotate all your structs that you want to serialize with #[derive(RustcEncodable, RustcDecodable)] and the compiler will implement the serialization code for you:

extern crate rustc_serialize;

#[derive(RustcEncodable, RustcDecodable, PartialEq)]
struct Entity {
    x: f32,
    y: f32,
}

#[derive(RustcEncodable, RustcDecodable, PartialEq)]
struct World {
    entities: Vec<Entity>
}

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

Binary format

Bincode is a binary encoder / decoder implementation in Rust.

Add bincode to your Cargo.toml file:

[dependencies]
bincode = "*"

And use it like this:

extern crate rustc_serialize;
extern crate bincode;

#[derive(RustcEncodable, RustcDecodable, PartialEq)]
struct Entity {
    x: f32,
    y: f32,
}

#[derive(RustcEncodable, RustcDecodable, PartialEq)]
struct World {
    entities: Vec<Entity>
}

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

    let encoded: Vec<u8> = bincode::encode(&world, bincode::SizeLimit::Infinite).unwrap();
    let decoded: World = bincode::decode(&encoded[..]).unwrap();

    assert!(world == decoded);
}

This example was taken from bincode's README.

Writing to disk

Here is how you can write to and load directly from files:

use std::fs::File;

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

    {
        let mut file = File::create("world.bin").unwrap();
        bincode::encode_into(&world, &mut file, bincode::SizeLimit::Infinite).unwrap();
    }

    let mut file = File::open("world.bin").unwrap();
    let decoded: World = bincode::decode_from(&mut file, bincode::SizeLimit::Infinite).unwrap();

    assert!(world == decoded);
}

Using BufWriter and BufReader are just as easy:

use std::fs::File;
use std::io::{BufWriter, BufReader};

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

    {
        let mut writer = BufWriter::new(File::create("world.bin").unwrap());
        bincode::encode_into(&world, &mut writer, bincode::SizeLimit::Infinite).unwrap();
    }

    let mut reader = BufReader::new(File::open("world.bin").unwrap());
    let decoded: World = bincode::decode_from(&mut reader, bincode::SizeLimit::Infinite).unwrap();

    assert!(world == decoded);
}

Compression

Since you can stream the bytes, it is easy to add compressions/decompression:

We are going to use the flate2 library.

Add flate2 to our Cargo.toml:

[dependencies]
flate2 = "*"

And use it like this:

extern crate flate2;

use std::fs::File;
use std::io::{BufWriter, BufReader};

use flate2::write::ZlibEncoder;
use flate2::read::ZlibDecoder;
use flate2::Compression;

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

    {
        let writer = BufWriter::new(File::create("world.bin.gz").unwrap());
        let mut encoder = ZlibEncoder::new(writer, Compression::Best);
        bincode::encode_into(&world, &mut encoder, bincode::SizeLimit::Infinite).unwrap();
    }

    let reader = BufReader::new(File::open("world.bin.gz").unwrap());
    let mut decoder = ZlibDecoder::new(reader);
    let decoded: World = bincode::decode_from(&mut decoder, bincode::SizeLimit::Infinite).unwrap();

    assert!(world == decoded);
}

Final remarks

Make sure to read up on SizeLimit which limits the amount of bytes that can be read or written.

Tested with

$ rustc --version
rustc 1.3.0-nightly (69ca01256 2015-07-23)