Bincode

シリアライゼーションRustバイナリ形式ネットワークプロトコル高性能

シリアライゼーションライブラリ

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エコシステムとの統合
  • セキュリティを考慮した設計(サイズ制限など)
  • アクティブなメンテナンス

デメリット

  • 人間が読めない形式
  • 独自プロトコルのみサポート(他のバイナリ形式との互換性なし)
  • データのバージョニングやファイルヘッダーは非対応
  • スキーマ情報を含まないため、送受信側で構造の共有が必要
  • デバッグが困難(バイナリ形式のため)

参考ページ

書き方の例

基本的な使用方法

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(())
}