Ciborium
シリアライゼーションライブラリ
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ベースのツールとの互換性がない
参考ページ
- GitHubリポジトリ: https://github.com/enarx/ciborium
- ドキュメント: https://docs.rs/ciborium
- CBOR仕様: https://tools.ietf.org/html/rfc7049
- Serde公式サイト: https://serde.rs/
書き方の例
基本的な使用方法
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(())
}