rust-protobuf

シリアライゼーションRustProtocol Buffersコード生成スキーマ

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

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と比べて人間が読みにくい
  • スキーマファイルの管理が必要
  • デバッグ時のデータ確認が困難

参考ページ

書き方の例

基本的な.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");
}