Ciborium

シリアライゼーションRustCBORserdeバイナリフォーマット

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

Ciborium

概要

Ciboriumは、Rustの標準的なシリアライゼーションフレームワークSerdeと統合されたCBOR(Concise Binary Object Representation)実装です。コンパクトで効率的なバイナリフォーマットによるデータ交換を実現し、最小限のエンコーディングサイズとワイヤー互換性を重視した設計となっています。

詳細

CBORは「Concise Binary Object Representation」の略で、JSONライクなデータ構造をより効率的にバイナリエンコードするための仕様です。CiboriumはRustにおける高品質なCBOR実装を提供し、Serdeエコシステムとの完全な統合を実現しています。

主な特徴:

  • 最小エンコーディング: 数値を最小限のバイト数で表現(例:1u128も1バイトで表現)
  • Serde統合: #[derive(Serialize, Deserialize)]でCBORサポートを自動追加
  • ワイヤー互換性: 他のCBOR実装との相互運用性を保証
  • 順序保持マップ: Vec<(Value, Value)>によるキー順序の保持
  • 安全性重視: 純粋なRustコードによる安全な実装

技術的詳細:

  • ロバスト原則: 受信時は寛容、送信時は厳格な動作
  • ゼロコピー対応: deserialize_ignored_anyによる効率的なデシリアライゼーション
  • スタックバッファ: 4KBのスタックバッファによる高速処理
  • 型安全性: Rustの型システムによる完全な型安全性保証

対応データ型:

  • 全てのプリミティブ型(整数、浮動小数点、文字列、バイト列)
  • コレクション型(Vec、HashMap、BTreeMap)
  • Option、Result、タプル
  • カスタム構造体とenum
  • 循環参照の検出と処理

メリット・デメリット

メリット

  • JSONより高速でコンパクトなバイナリフォーマット
  • Serdeとの完全統合による開発効率の向上
  • 他のCBOR実装との高い互換性
  • 純粋なRustコードによる安全性と移植性
  • 最小限のエンコーディングサイズ
  • 脆弱性のないセキュアな実装

デメリット

  • JSONと比べて人間が読みにくい
  • デバッグ時のデータ確認が困難
  • バイナリフォーマットのため、テキストエディタで編集不可
  • 既存のJSONベースのツールとの互換性がない

参考ページ

書き方の例

基本的な使用方法

use serde::{Serialize, Deserialize};
use ciborium::{de, ser};

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u32,
    email: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let person = Person {
        name: "Alice".to_string(),
        age: 30,
        email: "[email protected]".to_string(),
    };
    
    // CBORにシリアライズ
    let mut buffer = Vec::new();
    ser::into_writer(&person, &mut buffer)?;
    
    // CBORからデシリアライズ
    let deserialized: Person = de::from_reader(&buffer[..])?;
    println!("Deserialized: {:?}", deserialized);
    
    Ok(())
}

複雑なデータ構造の処理

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

#[derive(Serialize, Deserialize, Debug)]
struct ApiResponse {
    status: u16,
    message: String,
    data: HashMap<String, Vec<u64>>,
    metadata: Option<Metadata>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Metadata {
    version: String,
    timestamp: u64,
    flags: Vec<bool>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut data = HashMap::new();
    data.insert("ids".to_string(), vec![1, 2, 3, 4, 5]);
    data.insert("scores".to_string(), vec![95, 87, 92, 78, 88]);
    
    let response = ApiResponse {
        status: 200,
        message: "Success".to_string(),
        data,
        metadata: Some(Metadata {
            version: "1.0.0".to_string(),
            timestamp: 1634567890,
            flags: vec![true, false, true],
        }),
    };
    
    // CBORにシリアライズ
    let mut buffer = Vec::new();
    ser::into_writer(&response, &mut buffer)?;
    println!("Serialized {} bytes", buffer.len());
    
    // CBORからデシリアライズ
    let deserialized: ApiResponse = de::from_reader(&buffer[..])?;
    println!("Response: {:?}", deserialized);
    
    Ok(())
}

enumとVariant処理

use serde::{Serialize, Deserialize};
use ciborium::{de, ser};

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type")]
enum Event {
    Click { x: i32, y: i32 },
    KeyPress { key: String, modifiers: Vec<String> },
    Scroll { delta: f64 },
    Custom { name: String, payload: serde_json::Value },
}

#[derive(Serialize, Deserialize, Debug)]
struct EventLog {
    session_id: String,
    events: Vec<Event>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let events = vec![
        Event::Click { x: 100, y: 200 },
        Event::KeyPress { 
            key: "Enter".to_string(), 
            modifiers: vec!["Ctrl".to_string(), "Shift".to_string()],
        },
        Event::Scroll { delta: -120.0 },
        Event::Custom { 
            name: "analytics".to_string(), 
            payload: serde_json::json!({"user_id": 12345}),
        },
    ];
    
    let log = EventLog {
        session_id: "session_123".to_string(),
        events,
    };
    
    // CBORにシリアライズ
    let mut buffer = Vec::new();
    ser::into_writer(&log, &mut buffer)?;
    
    // CBORからデシリアライズ
    let deserialized: EventLog = de::from_reader(&buffer[..])?;
    println!("Event log: {:?}", deserialized);
    
    Ok(())
}

ストリーム処理

use serde::{Serialize, Deserialize};
use ciborium::{de, ser};
use std::io::{Write, Cursor};

#[derive(Serialize, Deserialize, Debug)]
struct Sensor {
    id: u32,
    temperature: f64,
    humidity: f64,
    timestamp: u64,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let sensors = vec![
        Sensor { id: 1, temperature: 23.5, humidity: 45.2, timestamp: 1634567890 },
        Sensor { id: 2, temperature: 24.1, humidity: 47.8, timestamp: 1634567891 },
        Sensor { id: 3, temperature: 22.9, humidity: 44.5, timestamp: 1634567892 },
    ];
    
    // 複数のセンサーデータをストリームにシリアライズ
    let mut buffer = Vec::new();
    for sensor in &sensors {
        ser::into_writer(sensor, &mut buffer)?;
    }
    
    // ストリームからデシリアライズ
    let mut cursor = Cursor::new(buffer);
    let mut deserialized_sensors = Vec::new();
    
    while cursor.position() < cursor.get_ref().len() as u64 {
        match de::from_reader(&mut cursor) {
            Ok(sensor) => deserialized_sensors.push(sensor),
            Err(_) => break,
        }
    }
    
    println!("Deserialized {} sensors", deserialized_sensors.len());
    for sensor in deserialized_sensors {
        println!("Sensor: {:?}", sensor);
    }
    
    Ok(())
}

エラーハンドリングとバリデーション

use serde::{Serialize, Deserialize};
use ciborium::{de, ser};

#[derive(Serialize, Deserialize, Debug)]
struct User {
    #[serde(rename = "userId")]
    id: u64,
    #[serde(rename = "userName")]
    name: String,
    #[serde(rename = "userEmail")]
    email: String,
    #[serde(default)]
    active: bool,
}

fn serialize_user(user: &User) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    let mut buffer = Vec::new();
    ser::into_writer(user, &mut buffer)?;
    Ok(buffer)
}

fn deserialize_user(data: &[u8]) -> Result<User, Box<dyn std::error::Error>> {
    let user: User = de::from_reader(data)?;
    
    // カスタムバリデーション
    if user.name.is_empty() {
        return Err("User name cannot be empty".into());
    }
    
    if !user.email.contains('@') {
        return Err("Invalid email format".into());
    }
    
    Ok(user)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let user = User {
        id: 1,
        name: "Alice".to_string(),
        email: "[email protected]".to_string(),
        active: true,
    };
    
    // シリアライズ
    let serialized = serialize_user(&user)?;
    println!("Serialized {} bytes", serialized.len());
    
    // デシリアライズ
    match deserialize_user(&serialized) {
        Ok(user) => println!("Valid user: {:?}", user),
        Err(e) => println!("Validation error: {}", e),
    }
    
    Ok(())
}

パフォーマンス最適化

use serde::{Serialize, Deserialize};
use ciborium::{de, ser};
use std::time::Instant;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct BenchmarkData {
    id: u64,
    values: Vec<f64>,
    metadata: String,
}

fn benchmark_cbor(data: &BenchmarkData, iterations: usize) -> Result<(), Box<dyn std::error::Error>> {
    let start = Instant::now();
    
    for _ in 0..iterations {
        // シリアライズ
        let mut buffer = Vec::new();
        ser::into_writer(data, &mut buffer)?;
        
        // デシリアライズ
        let _: BenchmarkData = de::from_reader(&buffer[..])?;
    }
    
    let duration = start.elapsed();
    println!("CBOR: {} iterations in {:?}", iterations, duration);
    println!("Average: {:?} per iteration", duration / iterations as u32);
    
    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let data = BenchmarkData {
        id: 12345,
        values: (0..1000).map(|i| i as f64 * 0.1).collect(),
        metadata: "benchmark_data".repeat(10),
    };
    
    benchmark_cbor(&data, 1000)?;
    
    Ok(())
}