rust-protobuf
シリアライゼーションライブラリ
rust-protobuf
概要
rust-protobufは、GoogleのProtocol Buffersの純粋なRust実装です。.protoファイルからRustコードを生成し、効率的なバイナリシリアライゼーションを提供します。動的メッセージ、テキストフォーマット、unknown fieldsの保持など、Protocol Buffersの完全な機能セットをサポートし、前方互換性を重視した設計となっています。
詳細
Protocol Buffersは、Googleが開発した言語中立・プラットフォーム中立な構造化データシリアライゼーションメカニズムです。rust-protobufは、このプロトコルの高品質なRust実装を提供し、C++のprotobufライブラリに依存しない純粋なRustソリューションを実現しています。
主な特徴:
- 純粋なRust実装: C++ライブラリに依存しない完全なRust実装
- コード生成: .protoファイルからRustコードを自動生成
- 動的メッセージ: 実行時に.protoファイルからメッセージを作成
- テキストフォーマット: 人間が読める形式での出力とパース
- unknown fields保持: 前方互換性のための不明フィールドの保持
技術的詳細:
- Messageトレイト: 全ての生成されたメッセージが実装する共通インターフェース
- キャッシュされたサイズ: シリアライゼーションパフォーマンスの向上
- EnumOrUnknown: 不明な列挙値の保持とi32値の完全サポート
- カスタムderive: 生成されたコードのカスタマイズ機能
バージョン情報:
- Version 3: 現在の安定版(stepancheg版)
- Version 4+: Google公式実装(別プロジェクト)
- Version 2: 重要なバグ修正のみ、メンテナンス終了
メリット・デメリット
メリット
- 効率的なバイナリシリアライゼーション
- 強力なスキーマ定義と型安全性
- 優れた前方・後方互換性
- 多言語間でのデータ交換に最適
- 純粋なRustコードによる高いパフォーマンス
- 豊富な機能セット(動的メッセージ、テキストフォーマット)
デメリット
- .protoファイルの学習コストがある
- コード生成ステップが必要
- JSONと比べて人間が読みにくい
- スキーマファイルの管理が必要
- デバッグ時のデータ確認が困難
参考ページ
- GitHubリポジトリ: https://github.com/stepancheg/rust-protobuf
- ドキュメント: https://docs.rs/protobuf
- Protocol Buffers公式サイト: https://developers.google.com/protocol-buffers
- The Rust Programming Language Guide: https://doc.rust-lang.org/book/
書き方の例
基本的な.protoファイル
// person.proto
syntax = "proto3";
package example;
message Person {
string name = 1;
int32 age = 2;
string email = 3;
repeated string phones = 4;
Address address = 5;
}
message Address {
string street = 1;
string city = 2;
string country = 3;
int32 zip_code = 4;
}
message People {
repeated Person people = 1;
}
基本的な使用方法
use protobuf::{Message, CodedInputStream, CodedOutputStream};
// 生成されたコードを使用
mod person_proto;
use person_proto::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// メッセージの作成
let mut person = Person::new();
person.set_name("Alice".to_string());
person.set_age(30);
person.set_email("[email protected]".to_string());
person.phones.push("123-456-7890".to_string());
person.phones.push("098-765-4321".to_string());
// アドレスの設定
let mut address = Address::new();
address.set_street("123 Main St".to_string());
address.set_city("Tokyo".to_string());
address.set_country("Japan".to_string());
address.set_zip_code(100_0001);
person.set_address(address);
// バイナリにシリアライズ
let serialized = person.write_to_bytes()?;
println!("Serialized {} bytes", serialized.len());
// バイナリからデシリアライズ
let deserialized = Person::parse_from_bytes(&serialized)?;
println!("Name: {}", deserialized.get_name());
println!("Age: {}", deserialized.get_age());
println!("Email: {}", deserialized.get_email());
println!("Phones: {:?}", deserialized.get_phones());
Ok(())
}
ストリーム処理
use protobuf::{Message, CodedInputStream, CodedOutputStream};
use std::io::{Write, Read, Cursor};
fn serialize_multiple_messages(people: &[Person]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut buffer = Vec::new();
let mut stream = CodedOutputStream::new(&mut buffer);
for person in people {
// メッセージサイズを書き込み
stream.write_raw_varint32(person.compute_size())?;
// メッセージを書き込み
person.write_to_with_cached_sizes(&mut stream)?;
}
stream.flush()?;
Ok(buffer)
}
fn deserialize_multiple_messages(data: &[u8]) -> Result<Vec<Person>, Box<dyn std::error::Error>> {
let mut cursor = Cursor::new(data);
let mut stream = CodedInputStream::new(&mut cursor);
let mut people = Vec::new();
while !stream.eof()? {
// メッセージサイズを読み取り
let size = stream.read_raw_varint32()?;
// メッセージを読み取り
let message_data = stream.read_raw_bytes(size)?;
let person = Person::parse_from_bytes(&message_data)?;
people.push(person);
}
Ok(people)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let people = vec![
{
let mut person = Person::new();
person.set_name("Alice".to_string());
person.set_age(30);
person.set_email("[email protected]".to_string());
person
},
{
let mut person = Person::new();
person.set_name("Bob".to_string());
person.set_age(25);
person.set_email("[email protected]".to_string());
person
},
];
// 複数のメッセージをシリアライズ
let serialized = serialize_multiple_messages(&people)?;
println!("Serialized {} messages in {} bytes", people.len(), serialized.len());
// 複数のメッセージをデシリアライズ
let deserialized = deserialize_multiple_messages(&serialized)?;
println!("Deserialized {} messages", deserialized.len());
for person in deserialized {
println!("Person: {} ({})", person.get_name(), person.get_age());
}
Ok(())
}
エラーハンドリングとバリデーション
use protobuf::{Message, ProtobufError};
use std::io::{self, ErrorKind};
fn validate_person(person: &Person) -> Result<(), String> {
if person.get_name().is_empty() {
return Err("Name cannot be empty".to_string());
}
if person.get_age() < 0 {
return Err("Age cannot be negative".to_string());
}
if !person.get_email().contains('@') {
return Err("Invalid email format".to_string());
}
if person.has_address() {
let address = person.get_address();
if address.get_country().is_empty() {
return Err("Country cannot be empty".to_string());
}
}
Ok(())
}
fn safe_serialize(person: &Person) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
// バリデーション
validate_person(person)?;
// シリアライズ
match person.write_to_bytes() {
Ok(data) => Ok(data),
Err(e) => Err(Box::new(e)),
}
}
fn safe_deserialize(data: &[u8]) -> Result<Person, Box<dyn std::error::Error>> {
// デシリアライズ
let person = Person::parse_from_bytes(data)?;
// バリデーション
validate_person(&person)?;
Ok(person)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut person = Person::new();
person.set_name("Alice".to_string());
person.set_age(30);
person.set_email("[email protected]".to_string());
// 安全なシリアライズ
match safe_serialize(&person) {
Ok(data) => {
println!("Serialized {} bytes", data.len());
// 安全なデシリアライズ
match safe_deserialize(&data) {
Ok(person) => println!("Valid person: {}", person.get_name()),
Err(e) => println!("Deserialization error: {}", e),
}
}
Err(e) => println!("Serialization error: {}", e),
}
Ok(())
}
テキストフォーマット
use protobuf::{Message, text_format};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut person = Person::new();
person.set_name("Alice".to_string());
person.set_age(30);
person.set_email("[email protected]".to_string());
let mut address = Address::new();
address.set_street("123 Main St".to_string());
address.set_city("Tokyo".to_string());
address.set_country("Japan".to_string());
address.set_zip_code(100_0001);
person.set_address(address);
// テキストフォーマットで出力
let text = text_format::print_to_string(&person);
println!("Text format:\n{}", text);
// テキストフォーマットからパース
let mut parsed_person = Person::new();
text_format::parse_from_str(&text, &mut parsed_person)?;
println!("Parsed name: {}", parsed_person.get_name());
println!("Parsed age: {}", parsed_person.get_age());
Ok(())
}
動的メッセージ(概念例)
use protobuf::{Message, MessageDescriptor, MessageFactory};
// 注意: 実際のAPIは異なる場合があります
fn work_with_dynamic_messages() -> Result<(), Box<dyn std::error::Error>> {
// .protoファイルから動的にメッセージを作成
// この例は概念的なものです
// let descriptor = MessageDescriptor::from_proto_file("person.proto")?;
// let factory = MessageFactory::new();
// let mut message = factory.create_message(&descriptor)?;
// // 動的にフィールドを設定
// message.set_field_by_name("name", "Alice")?;
// message.set_field_by_name("age", 30)?;
// // シリアライズ
// let serialized = message.write_to_bytes()?;
// // デシリアライズ
// let deserialized = factory.parse_from_bytes(&descriptor, &serialized)?;
Ok(())
}
パフォーマンス最適化とベンチマーク
use protobuf::Message;
use std::time::Instant;
fn benchmark_protobuf(person: &Person, iterations: usize) -> Result<(), Box<dyn std::error::Error>> {
let start = Instant::now();
for _ in 0..iterations {
// シリアライズ
let serialized = person.write_to_bytes()?;
// デシリアライズ
let _deserialized = Person::parse_from_bytes(&serialized)?;
}
let duration = start.elapsed();
println!("Protocol Buffers: {} iterations in {:?}", iterations, duration);
println!("Average: {:?} per iteration", duration / iterations as u32);
Ok(())
}
fn compare_sizes(person: &Person) -> Result<(), Box<dyn std::error::Error>> {
// Protocol Buffersサイズ
let pb_data = person.write_to_bytes()?;
// JSONサイズ(比較用)
let json_data = serde_json::to_vec(person)?;
println!("Protocol Buffers size: {} bytes", pb_data.len());
println!("JSON size: {} bytes", json_data.len());
println!("Size reduction: {:.1}%",
(1.0 - pb_data.len() as f64 / json_data.len() as f64) * 100.0);
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut person = Person::new();
person.set_name("Alice".to_string());
person.set_age(30);
person.set_email("[email protected]".to_string());
// 大きなデータセットを作成
for i in 0..100 {
person.phones.push(format!("123-456-{:04}", i));
}
benchmark_protobuf(&person, 1000)?;
compare_sizes(&person)?;
Ok(())
}
build.rsでのコード生成
// build.rs
use protobuf_codegen::Codegen;
fn main() {
Codegen::new()
.out_dir("src/generated")
.inputs(&["proto/person.proto"])
.include("proto")
.run()
.expect("Codegen failed");
}