Rust Protocol Buffers解析库pb-rs的使用,高效实现.proto文件到Rust代码的转换

Rust Protocol Buffers解析库pb-rs的使用,高效实现.proto文件到Rust代码的转换

pb-rs是一个简单的转换工具,可以将.proto文件转换为与quick-protobuf兼容的Rust模块。

使用方法

命令行使用

pb-rs <file.proto>

作为库使用(例如在cargo构建脚本中)

Cargo.toml中添加依赖:

[dependencies]
quick-protobuf = "0.8.0"

[build-dependencies]
pb-rs = "0.9.1"

build.rs构建脚本示例:

use pb_rs::{types::FileDescriptor, ConfigBuilder};
use std::path::{Path, PathBuf};
use walkdir::WalkDir;

fn main() {
    // 设置输出目录
    let out_dir = std::env::var("OUT_DIR").unwrap();
    let out_dir = Path::new(&out_dir).join("protos");

    // 设置输入目录
    let in_dir = PathBuf::from(::std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("protos");
    // 如果protos目录有变化,重新运行构建脚本
    println!("cargo:rerun-if-changed={}", in_dir.to_str().unwrap());

    // 查找所有.proto文件
    let mut protos = Vec::new();
    let proto_ext = Some(Path::new("proto").as_os_str());
    for entry in WalkDir::new(&in_dir) {
        let path = entry.unwrap().into_path();
        if path.extension() == proto_ext {
            // 如果.proto文件有变化,重新运行构建脚本
            println!("cargo:rerun-if-changed={}", path.to_str().unwrap());
            protos.push(path);
        }
    }

    // 删除旧的生成文件
    if out_dir.exists() {
        std::fs::remove_dir_all(&out_dir).unwrap();
    }
    std::fs::DirBuilder::new().create(&out_dir).unwrap();
    
    // 配置并运行代码生成
    let config_builder = ConfigBuilder::new(&protos, None, Some(&out_dir), &[in_dir]).unwrap();
    FileDescriptor::run(&config_builder.build()).unwrap()
}

main.rslib.rs中使用生成的代码:

mod hello {
    include_bytes!(concat!(env!("OUT_DIR"), "/hello.rs"));
}

完整示例

项目结构

my_project/
├── Cargo.toml
├── build.rs
├── src/
│   └── main.rs
└── protos/
    └── hello.proto

hello.proto 文件内容

syntax = "proto3";

message Hello {
    string name = 1;
    int32 age = 2;
}

build.rs 文件

use pb_rs::{types::FileDescriptor, ConfigBuilder};
use std::path::Path;

fn main() {
    let out_dir = std::env::var("OUT_DIR").unwrap();
    let proto_file = "protos/hello.proto";
    
    // 设置重新构建条件
    println!("cargo:rerun-if-changed={}", proto_file);
    
    // 配置并生成代码
    let config = ConfigBuilder::new(&[proto_file], None, Some(&out_dir), &["protos"])
        .unwrap()
        .build();
    FileDescriptor::run(&config).unwrap();
}

main.rs 文件

mod hello {
    include!(concat!(env!("OUT_DIR"), "/hello.rs"));
}

use hello::Hello;
use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};

fn main() {
    // 创建消息
    let mut hello = Hello::default();
    hello.name = "World".to_string();
    hello.age = 42;

    // 序列化
    let mut buf = Vec::new();
    let mut writer = Writer::new(&mut buf);
    hello.write_message(&mut writer).unwrap();

    // 反序列化
    let mut reader = BytesReader::from_bytes(&buf);
    let hello_deserialized = Hello::from_reader(&mut reader, &buf).unwrap();

    println!("Deserialized: name={}, age={}", 
        hello_deserialized.name, 
        hello_deserialized.age);
}

安装

安装命令行工具

cargo install pb-rs

作为库安装

在项目目录中运行:

cargo add pb-rs

或在Cargo.toml中添加:

pb-rs = "0.10.0"

pb-rs提供了一种高效的方式将Protocol Buffers的.proto文件转换为Rust代码,与quick-protobuf库配合使用可以实现高效的消息序列化和反序列化。


1 回复

Rust Protocol Buffers解析库pb-rs的使用

pb-rs是一个高效的Protocol Buffers解析库,专门用于将.proto文件转换为Rust代码。它为Rust开发者提供了简单易用的方式来处理Protocol Buffers数据格式。

主要特性

  • 支持proto2和proto3语法
  • 生成轻量级的Rust结构体
  • 高性能的编解码实现
  • 支持gRPC代码生成
  • 可定制的代码生成选项

安装方法

首先需要在Cargo.toml中添加依赖:

[dependencies]
pb-rs = "0.9"
prost = "0.11"

基本使用方法

1. 创建.proto文件

创建一个简单的proto文件person.proto:

syntax = "proto3";

message Person {
    string name = 1;
    int32 id = 2;
    string email = 3;
}

2. 使用pb-rs生成Rust代码

创建一个build.rs文件:

fn main() {
    pb_rs::run(pb_rs::Config {
        input: &["src/protos/person.proto"],
        includes: &["src/protos"],
        output: "src/person.rs",
        single_module: true,
        ..Default::default()
    }).expect("Code generation failed");
}

3. 在项目中使用生成的代码

在lib.rs或main.rs中引入生成的模块:

mod person;
pub use person::*;

4. 使用生成的Rust结构体

use prost::Message;

fn main() {
    // 创建Person对象
    let mut person = Person {
        name: "Alice".to_string(),
        id: 1234,
        email: "alice@example.com".to_string(),
    };
    
    // 序列化为字节
    let mut buf = Vec::new();
    person.encode(&mut buf).unwrap();
    
    // 反序列化
    let decoded_person = Person::decode(&buf[..]).unwrap();
    
    println!("Decoded person: {:?}", decoded_person);
}

高级用法

自定义代码生成选项

fn main() {
    pb_rs::run(pb-rs::Config {
        input: &["src/protos/*.proto"],
        includes: &["src/protos"],
        output: "src/protos/mod.rs",
        headers: true,
        dont_use_cow: true,
        custom_derive: "#[derive(Serialize, Deserialize)]",
        ..Default::default()
    }).expect("Code generation failed");
}

处理枚举类型

proto文件:

enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
}

message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
}

生成的Rust代码会自动包含枚举类型,可以直接使用:

let phone = PhoneNumber {
    number: "555-1234".to_string(),
    r#type: PhoneType::Home.into(),
};

与gRPC集成

pb-rs也可以生成gRPC服务代码。首先定义服务:

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

然后在build.rs中启用gRPC支持:

pb_rs::run(pb_rs::Config {
    input: &["src/protos/greeter.proto"],
    includes: &["src/protos"],
    output: "src/greeter.rs",
    generate_server: true,
    generate_client: true,
    ..Default::default()
}).expect("Code generation failed");

性能提示

  • 对于大型消息,考虑使用bytes::Bytes而不是Vec<u8>
  • 启用dont_use_cow选项可以提高某些场景下的性能
  • 使用prost::Message trait提供的方法进行高效编解码

pb-rs为Rust开发者提供了高效、类型安全的Protocol Buffers支持,是处理.proto文件的优秀选择。

完整示例demo

以下是一个完整的pb-rs使用示例,包含从定义proto文件到实际使用的完整流程:

  1. 项目结构
my_project/
├── Cargo.toml
├── build.rs
├── src/
│   ├── main.rs
│   └── protos/
│       └── addressbook.proto
  1. addressbook.proto
syntax = "proto3";

message Person {
    string name = 1;
    int32 id = 2;
    string email = 3;
    
    enum PhoneType {
        MOBILE = 0;
        HOME = 1;
        WORK = 2;
    }
    
    message PhoneNumber {
        string number = 1;
        PhoneType type = 2;
    }
    
    repeated PhoneNumber phones = 4;
}

message AddressBook {
    repeated Person people = 1;
}
  1. build.rs
fn main() {
    pb_rs::run(pb_rs::Config {
        input: &["src/protos/addressbook.proto"],
        includes: &["src/protos"],
        output: "src/addressbook.rs",
        headers: true,
        custom_derive: "#[derive(serde::Serialize, serde::Deserialize)]",
        ..Default::default()
    }).expect("protobuf codegen failed");
}
  1. Cargo.toml
[package]
name = "pb-rs-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
pb-rs = "0.9"
prost = "0.11"
serde = { version = "1.0", features = ["derive"] }

[build-dependencies]
pb-rs = "0.9"
  1. src/main.rs
mod addressbook;
use addressbook::*;
use prost::Message;

fn main() {
    // 创建一个联系人
    let mut person = Person {
        name: "John Doe".to_string(),
        id: 1234,
        email: "john@example.com".to_string(),
        phones: vec![
            Person_PhoneNumber {
                number: "555-4321".to_string(),
                r#type: Person_PhoneType::Home.into(),
            },
            Person_PhoneNumber {
                number: "555-1234".to_string(),
                r#type: Person_PhoneType::Mobile.into(),
            }
        ],
    };
    
    // 创建通讯录
    let mut address_book = AddressBook {
        people: vec![person],
    };
    
    // 序列化
    let mut buf = Vec::new();
    address_book.encode(&mut buf).unwrap();
    println!("Serialized data ({} bytes): {:?}", buf.len(), buf);
    
    // 反序列化
    let decoded = AddressBook::decode(&buf[..]).unwrap();
    println!("Deserialized: {:?}", decoded);
    
    // 访问数据
    if let Some(first_person) = decoded.people.first() {
        println!("First person's name: {}", first_person.name);
        for phone in &first_person.phones {
            println!("Phone: {}, Type: {:?}", phone.number, phone.r#type());
        }
    }
}

这个完整示例展示了pb-rs的典型使用场景,包括:

  • 定义复杂的proto消息结构
  • 生成Rust代码
  • 序列化和反序列化数据
  • 访问生成的Rust结构体中的嵌套类型和枚举
回到顶部