Bincode
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
- GitHub Repository: https://github.com/bincode-org/bincode
- Documentation: https://docs.rs/bincode/
- Crates.io: https://crates.io/crates/bincode/
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(())
}