Bincode
シリアライゼーションライブラリ
Bincode
概要
Bincodeは、Rust用のコンパクトなバイナリシリアライゼーションライブラリです。ゼロオーバーヘッドのバイナリエンコーディングにより、メモリ上のデータ構造と同じかそれ以下のサイズでシリアライズできます。ネットワークプロトコル、IPC(プロセス間通信)、設定ファイルの保存など、パフォーマンスとサイズが重要な場面で活用されています。
詳細
Bincodeは、メタデータを含まないコンパクトなバイナリ形式でデータをエンコード・デコードするライブラリです。構造体のフィールド名などの情報を保存せず、純粋なバイナリデータストリームとして出力するため、非常に効率的です。
主な特徴:
- コンパクトな表現: メモリ上のサイズと同等またはそれ以下のエンコードサイズ
- 高速処理: JSONと比較して226倍高速なエンコード、48倍高速なデコード
- バイトオーダー非依存: 異なるアーキテクチャ間でのデータ交換が可能
- ストリームAPI: ファイルやネットワークストリームとの統合が容易
- 安定したフォーマット: 同じ設定なら後方互換性を維持
技術的詳細:
- enum variantは通常u32でエンコード(可変長整数エンコーディング有効時はu8)
- isize/usizeはポータビリティのためi64/u64として エンコード
- 最大サイズ制限の設定により、悪意のあるデータからの保護が可能
- Bincode 2からSerdeはオプション依存となった
使用例:
- Google Tarpc: RPCメッセージのシリアライゼーション
- Servo IPC-Channel: プロセス間通信
- 設定ファイルの保存
- ゲームのセーブデータ
メリット・デメリット
メリット
- 極めて高いパフォーマンス(エンコード・デコード速度)
- 非常にコンパクトなデータサイズ(JSONと比較して73%削減)
- バイトオーダー非依存による優れた互換性
- Serdeエコシステムとの統合
- セキュリティを考慮した設計(サイズ制限など)
- アクティブなメンテナンス
デメリット
- 人間が読めない形式
- 独自プロトコルのみサポート(他のバイナリ形式との互換性なし)
- データのバージョニングやファイルヘッダーは非対応
- スキーマ情報を含まないため、送受信側で構造の共有が必要
- デバッグが困難(バイナリ形式のため)
参考ページ
- GitHubリポジトリ: https://github.com/bincode-org/bincode
- ドキュメント: https://docs.rs/bincode/
- Crates.io: https://crates.io/crates/bincode/
書き方の例
基本的な使用方法
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 };
// バイナリへシリアライズ
let encoded: Vec<u8> = bincode::serialize(&point)?;
println!("Encoded size: {} bytes", encoded.len());
println!("Encoded data: {:?}", encoded);
// デシリアライズ
let decoded: Point = bincode::deserialize(&encoded)?;
println!("Decoded: {:?}", decoded);
assert_eq!(point, decoded);
Ok(())
}
ネットワーク通信での使用
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,
}
// サーバー側
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);
// レスポンスを送信
let response = Message::Chat {
text: "Message received".to_string(),
};
let encoded = bincode::serialize(&response)?;
stream.write_all(&encoded)?;
Ok(())
}
// クライアント側
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(())
}
ファイルの読み書き
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_game(&save, "game.save")?;
// ロード
let loaded = load_game("game.save")?;
println!("Loaded save: {:?}", loaded);
Ok(())
}
設定オプションの使用
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(),
};
// デフォルト設定
let encoded_default = bincode::serialize(&data)?;
println!("Default encoding size: {} bytes", encoded_default.len());
// カスタム設定(可変長整数エンコーディング有効)
let config = bincode::config();
let encoded_varint = config
.with_varint_encoding()
.serialize(&data)?;
println!("Varint encoding size: {} bytes", encoded_varint.len());
// サイズ制限付き設定(セキュリティ対策)
let limited_config = bincode::config();
let result = limited_config
.with_limit(100) // 100バイト制限
.serialize(&data);
match result {
Ok(encoded) => println!("Encoded within limit"),
Err(e) => println!("Size limit exceeded: {}", e),
}
Ok(())
}
複雑な型の処理
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,
})),
};
// シリアライズ
let encoded = bincode::serialize(&data)?;
println!("Encoded size: {} bytes", encoded.len());
// デシリアライズ
let decoded: ComplexData = bincode::deserialize(&encoded)?;
println!("Decoded: {:#?}", decoded);
Ok(())
}
圧縮との組み合わせ
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(())
}