Bincode

serializationRustbinary-formatnetwork-protocolshigh-performance

Serialization Library

Bincode

Overview

Bincode is a compact binary serialization library for Rust. With zero-overhead binary encoding, it can serialize data to the same size or smaller than the in-memory data structure. It's used in scenarios where performance and size matter, such as network protocols, IPC (Inter-Process Communication), and configuration file storage.

Details

Bincode is a library that encodes and decodes data in a compact binary format without metadata. It outputs pure binary data streams without storing information like struct field names, making it highly efficient.

Key Features:

  • Compact Representation: Encoding size equal to or smaller than in-memory size
  • High Performance: 226x faster encoding and 48x faster decoding compared to JSON
  • Byte-Order Independent: Enables data exchange between different architectures
  • Stream API: Easy integration with files and network streams
  • Stable Format: Maintains backward compatibility with the same configuration

Technical Details:

  • Enum variants encoded as u32 (u8 with variable-length integer encoding)
  • isize/usize encoded as i64/u64 for portability
  • Protection against malicious data through maximum size limits
  • Serde became an optional dependency since Bincode 2

Usage Examples:

  • Google Tarpc: RPC message serialization
  • Servo IPC-Channel: Inter-process communication
  • Configuration file storage
  • Game save data

Pros and Cons

Pros

  • Extremely high performance (encoding/decoding speed)
  • Very compact data size (73% reduction compared to JSON)
  • Excellent compatibility through byte-order independence
  • Integration with Serde ecosystem
  • Security-conscious design (size limits, etc.)
  • Active maintenance

Cons

  • Not human-readable format
  • Only supports its own protocol (no compatibility with other binary formats)
  • No data versioning or file header support
  • Requires shared structure between sender and receiver (no schema information)
  • Difficult to debug (binary format)

References

Code Examples

Basic Usage

use serde::{Serialize, Deserialize};
use bincode;

#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let point = Point { x: 10, y: 20 };
    
    // Serialize to binary
    let encoded: Vec<u8> = bincode::serialize(&point)?;
    println!("Encoded size: {} bytes", encoded.len());
    println!("Encoded data: {:?}", encoded);
    
    // Deserialize
    let decoded: Point = bincode::deserialize(&encoded)?;
    println!("Decoded: {:?}", decoded);
    
    assert_eq!(point, decoded);
    
    Ok(())
}

Network Communication

use serde::{Serialize, Deserialize};
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};

#[derive(Serialize, Deserialize, Debug)]
enum Message {
    Connect { username: String },
    Chat { text: String },
    Disconnect,
}

// Server side
fn handle_client(mut stream: TcpStream) -> Result<(), Box<dyn std::error::Error>> {
    let mut buffer = Vec::new();
    stream.read_to_end(&mut buffer)?;
    
    let message: Message = bincode::deserialize(&buffer)?;
    println!("Received: {:?}", message);
    
    // Send response
    let response = Message::Chat {
        text: "Message received".to_string(),
    };
    let encoded = bincode::serialize(&response)?;
    stream.write_all(&encoded)?;
    
    Ok(())
}

// Client side
fn send_message(address: &str) -> Result<(), Box<dyn std::error::Error>> {
    let mut stream = TcpStream::connect(address)?;
    
    let message = Message::Connect {
        username: "Alice".to_string(),
    };
    
    let encoded = bincode::serialize(&message)?;
    stream.write_all(&encoded)?;
    
    Ok(())
}

File I/O

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

#[derive(Serialize, Deserialize, Debug)]
struct GameSave {
    player_name: String,
    level: u32,
    score: u64,
    inventory: Vec<String>,
    position: (f32, f32, f32),
}

fn save_game(save_data: &GameSave, path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let file = File::create(path)?;
    let writer = BufWriter::new(file);
    bincode::serialize_into(writer, save_data)?;
    Ok(())
}

fn load_game(path: &str) -> Result<GameSave, Box<dyn std::error::Error>> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let save_data = bincode::deserialize_from(reader)?;
    Ok(save_data)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let save = GameSave {
        player_name: "Hero".to_string(),
        level: 42,
        score: 999999,
        inventory: vec!["Sword".to_string(), "Shield".to_string(), "Potion".to_string()],
        position: (100.0, 50.0, 0.0),
    };
    
    // Save
    save_game(&save, "game.save")?;
    
    // Load
    let loaded = load_game("game.save")?;
    println!("Loaded save: {:?}", loaded);
    
    Ok(())
}

Configuration Options

use bincode::{Config, Options};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Data {
    values: Vec<u64>,
    text: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = Data {
        values: vec![1, 2, 3, 4, 5],
        text: "Hello, Bincode!".to_string(),
    };
    
    // Default configuration
    let encoded_default = bincode::serialize(&data)?;
    println!("Default encoding size: {} bytes", encoded_default.len());
    
    // Custom configuration (variable-length integer encoding enabled)
    let config = bincode::config();
    let encoded_varint = config
        .with_varint_encoding()
        .serialize(&data)?;
    println!("Varint encoding size: {} bytes", encoded_varint.len());
    
    // Configuration with size limit (security measure)
    let limited_config = bincode::config();
    let result = limited_config
        .with_limit(100) // 100 byte limit
        .serialize(&data);
    
    match result {
        Ok(encoded) => println!("Encoded within limit"),
        Err(e) => println!("Size limit exceeded: {}", e),
    }
    
    Ok(())
}

Handling Complex Types

use serde::{Serialize, Deserialize};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Debug)]
enum Status {
    Active,
    Inactive,
    Pending(String),
}

#[derive(Serialize, Deserialize, Debug)]
struct ComplexData {
    id: u64,
    status: Status,
    metadata: HashMap<String, String>,
    nested: Option<Box<ComplexData>>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut metadata = HashMap::new();
    metadata.insert("created".to_string(), "2024-01-01".to_string());
    metadata.insert("type".to_string(), "example".to_string());
    
    let data = ComplexData {
        id: 1,
        status: Status::Pending("Processing".to_string()),
        metadata: metadata.clone(),
        nested: Some(Box::new(ComplexData {
            id: 2,
            status: Status::Active,
            metadata,
            nested: None,
        })),
    };
    
    // Serialize
    let encoded = bincode::serialize(&data)?;
    println!("Encoded size: {} bytes", encoded.len());
    
    // Deserialize
    let decoded: ComplexData = bincode::deserialize(&encoded)?;
    println!("Decoded: {:#?}", decoded);
    
    Ok(())
}

Combining with Compression

use bincode;
use flate2::Compression;
use flate2::write::{GzEncoder, GzDecoder};
use std::io::Write;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct LargeData {
    numbers: Vec<i32>,
    text: String,
}

fn compress_bincode<T: Serialize>(data: &T) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let encoded = bincode::serialize(data)?;
    
    let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
    encoder.write_all(&encoded)?;
    let compressed = encoder.finish()?;
    
    Ok(compressed)
}

fn decompress_bincode<T: for<'de> Deserialize<'de>>(
    compressed: &[u8]
) -> Result<T, Box<dyn std::error::Error>> {
    let mut decoder = GzDecoder::new(Vec::new());
    decoder.write_all(compressed)?;
    let decompressed = decoder.finish()?;
    
    let data = bincode::deserialize(&decompressed)?;
    Ok(data)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = LargeData {
        numbers: (0..1000).collect(),
        text: "This is a test string that will be compressed.".repeat(100),
    };
    
    let original_size = bincode::serialize(&data)?.len();
    let compressed = compress_bincode(&data)?;
    let compressed_size = compressed.len();
    
    println!("Original size: {} bytes", original_size);
    println!("Compressed size: {} bytes", compressed_size);
    println!("Compression ratio: {:.2}%", 
        (compressed_size as f64 / original_size as f64) * 100.0);
    
    let decompressed: LargeData = decompress_bincode(&compressed)?;
    println!("Successfully decompressed {} numbers", decompressed.numbers.len());
    
    Ok(())
}