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.rs
或lib.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文件到实际使用的完整流程:
- 项目结构
my_project/
├── Cargo.toml
├── build.rs
├── src/
│ ├── main.rs
│ └── protos/
│ └── addressbook.proto
- 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;
}
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");
}
- 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"
- 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结构体中的嵌套类型和枚举