1 个不稳定版本
0.11.3 | 2024年4月28日 |
---|
#544 in 编码
110KB
2.5K SLoC
PROST!
prost
是 Protocol Buffers 的 Rust 语言实现。 prost
从 proto2
和 proto3
文件生成简单的、惯用的 Rust 代码。
与其他 Protocol Buffers 实现相比,prost
- 通过利用 Rust 的
derive
属性,生成简单、惯用且易于阅读的 Rust 类型。 - 保留
.proto
文件中的注释在生成的 Rust 代码中。 - 允许通过添加属性来序列化和反序列化现有的 Rust 类型(不是从
.proto
生成的)。 - 使用
bytes::{Buf, BufMut}
抽象进行序列化,而不是使用std::io::{Read, Write}
。 - 在组织生成的代码到 Rust 模块时尊重 Protobuf 的
package
规范。 - 在反序列化过程中保留未知枚举值。
- 不包含对运行时反射或消息描述符的支持。
在 Cargo 项目中使用 prost
首先,将 prost
及其公共依赖项添加到您的 Cargo.toml
[dependencies]
prost = "0.10"
# Only necessary if using Protobuf well-known types:
prost-types = "0.10"
将 .proto
编译添加到 Cargo 项目中推荐的方法是使用 prost-build
库。有关更多详细信息和方法示例,请参阅 prost-build
文档。
有关从头到尾的简单示例,请参阅 snazzy 存储库。
MSRV
prost
遵循 tokio-rs
项目的 MSRV 模型,支持 1.56+。有关 tokio msrv 策略的更多信息,您可以在此处查看 这里。
生成代码
prost
使用 proto2
或 proto3
语法从源 .proto
文件生成 Rust 代码。prost
的目标是使生成的代码尽可能简单。
protoc
在 prost-build
v0.11 版本发布后,将需要 protoc
来调用 compile_protos
(除非启用了 skip_protoc
)。Prost 将不再为用户提供捆绑的 protoc
或尝试为用户编译 protoc
。有关 protoc
的安装说明,请参阅 protobuf 安装说明。
包
Prost 现在可以为没有包规范的 .proto
文件生成代码。 prost
将 Protobuf 包翻译成 Rust 模块。例如,给定 package
指令
package foo.bar;
从文件生成的所有 Rust 类型都将位于 foo::bar
模块中。
消息
给定一个简单的消息声明
// Sample message.
message Foo {
}
prost
将生成以下 Rust 结构体
/// Sample message.
#[derive(Clone, Debug, PartialEq, Message)]
pub struct Foo {
}
字段
Protobuf 消息中的字段被转换为 Rust 中对应类型的公共结构体字段。
标量值
标量值类型转换为以下格式
Protobuf 类型 | Rust 类型 |
---|---|
double |
f64 |
float |
f32 |
int32 |
i32 |
int64 |
i64 |
uint32 |
u32 |
uint64 |
u64 |
sint32 |
i32 |
sint64 |
i64 |
fixed32 |
u32 |
fixed64 |
u64 |
sfixed32 |
i32 |
sfixed64 |
i64 |
bool |
bool |
string |
String |
bytes |
Vec<u8> |
枚举
所有 .proto
枚举类型都转换为 Rust 的 i32
类型。此外,每个枚举类型都有一个对应的 Rust enum
类型。例如,此 proto
枚举
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
对应于以下 Rust 枚举 [1]
pub enum PhoneType {
Mobile = 0,
Home = 1,
Work = 2,
}
您可以通过以下方式将 PhoneType
值转换为 i32
PhoneType::Mobile as i32
添加到生成的 PhoneType
中的 #[derive(::prost::Enumeration)]
注解为类型添加了以下关联函数
impl PhoneType {
pub fn is_valid(value: i32) -> bool { ... }
pub fn from_i32(value: i32) -> Option<PhoneType> { ... }
}
因此,您可以通过以下方式将 i32
转换为其对应的 PhoneType
值,例如
let phone_type = 2i32;
match PhoneType::from_i32(phone_type) {
Some(PhoneType::Mobile) => ...,
Some(PhoneType::Home) => ...,
Some(PhoneType::Work) => ...,
None => ...,
}
此外,当在 Message
中使用 proto
枚举作为字段时,该消息将具有获取/设置字段值的 '访问器' 方法,作为 Rust 枚举类型。例如,这个具有名为 type
字段的 PhoneNumber
消息,该字段类型为 PhoneType
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
将变成以下 Rust 类型 [1],具有 type
和 set_type
方法
pub struct PhoneNumber {
pub number: String,
pub r#type: i32, // the `r#` is needed because `type` is a Rust keyword
}
impl PhoneNumber {
pub fn r#type(&self) -> PhoneType { ... }
pub fn set_type(&mut self, value: PhoneType) { ... }
}
请注意,如果字段具有无效的 i32
值,则获取器方法将返回 Rust 枚举的默认值。
枚举类型不直接用作字段,因为 Protobuf 规范要求枚举值是 '开放的',并且必须能够解码未识别的枚举值。
[1] 为了清晰起见,省略了注释。请参见下面的完整示例。
字段修饰符
Protobuf 标量值和枚举消息字段可以有一个修饰符,这取决于 Protobuf 版本。修饰符会改变 Rust 字段的相应类型
.proto 版本 |
修饰符 | Rust 类型 |
---|---|---|
proto2 |
可选 |
Option<T> |
proto2 |
必需 |
T |
proto3 |
默认 | T 用于标量类型,否则为 Option<T> |
proto3 |
可选 |
Option<T> |
proto2 /proto3 |
重复 |
Vec<T> |
请注意,在 proto3
中,所有用户定义的消息类型的默认表示形式是 Option<T>
,而对于标量类型则是 T
(在解码过程中,缺失的值由 T::default()
填充)。如果您需要一个标量类型 T
的存在证明,请使用 optional
修饰符在生成的 Rust 结构体中强制执行 Option<T>
表示形式。
映射字段
映射字段被转换为 Rust HashMap
,键和值类型从 Protobuf 键和值类型转换而来。
消息字段
消息字段被转换为相应的结构体类型。上面的字段修饰符表适用于消息字段,但 proto3
消息字段(默认修饰符)将被包裹在一个 Option
中。通常,消息字段是无包装的。如果字段类型和父类型递归嵌套,则 prost
将自动将消息字段包装起来,以避免无限大小的结构体。
Oneof 字段
Oneof 字段转换为 Rust 枚举。由于 Protobuf 的 oneof
类型没有命名,所以 prost
使用 oneof
字段的名称作为生成的 Rust 枚举的名称,并在结构体下的模块中定义该枚举。例如,一个 proto3
消息,如下所示
message Foo {
oneof widget {
int32 quux = 1;
string bar = 2;
}
}
生成以下 Rust[1]
pub struct Foo {
pub widget: Option<foo::Widget>,
}
pub mod foo {
pub enum Widget {
Quux(i32),
Bar(String),
}
}
oneof
字段总是被包裹在一个 Option
中。
[1] 为了清晰起见,省略了注释。请参见下面的完整示例。
服务
prost-build
允许使用自定义代码生成器来处理 service
定义。这可以用于根据应用程序的特定需求输出 Rust 特性。
生成的代码示例
示例 .proto
文件
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
以及生成的Rust代码(tutorial.rs
)
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Person {
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
/// Unique ID number for this person.
#[prost(int32, tag="2")]
pub id: i32,
#[prost(string, tag="3")]
pub email: ::prost::alloc::string::String,
#[prost(message, repeated, tag="4")]
pub phones: ::prost::alloc::vec::Vec<person::PhoneNumber>,
}
/// Nested message and enum types in `Person`.
pub mod person {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PhoneNumber {
#[prost(string, tag="1")]
pub number: ::prost::alloc::string::String,
#[prost(enumeration="PhoneType", tag="2")]
pub r#type: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum PhoneType {
Mobile = 0,
Home = 1,
Work = 2,
}
}
/// Our address book file is just one of these.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AddressBook {
#[prost(message, repeated, tag="1")]
pub people: ::prost::alloc::vec::Vec<Person>,
}
访问protoc
FileDescriptorSet
可以使用prost_build::Config::file_descriptor_set_path
选项在构建和代码生成步骤中输出文件描述符集。当与std::include_bytes
宏和prost_types::FileDescriptorSet
类型一起使用时,使用Prost的应用程序和库可以实现需要从原始.proto
文件中获取详细信息的反射功能。
在no_std
Crate中使用prost
prost
与no_std
Crates兼容。要启用no_std
支持,请在prost
和prost-types
中禁用std
功能。
[dependencies]
prost = { version = "0.6", default-features = false, features = ["prost-derive"] }
# Only necessary if using Protobuf well-known types:
prost-types = { version = "0.6", default-features = false }
此外,在build.rs
中配置prost-build
以输出所有Protobuf map
字段的BTreeMap
而不是HashMap
。
let mut config = prost_build::Config::new();
config.btree_map(&["."]);
当使用2015版时,可能需要在包含prost
-生成的代码的crate中添加extern crate core;
指令。
序列化现有类型
prost
使用自定义 derive 宏来处理编码和解码类型,这意味着如果您的现有 Rust 类型与 Protobuf 类型兼容,则可以通过添加适当的 derive 和字段注解来序列化和反序列化它。
目前,关于添加注解的最好文档是查看上面的生成代码示例。
现有类型的标签推断
Prost会自动为结构体推断标签。
字段按指定顺序顺序标记,从1
开始。
您可以通过在第一个字段后的间隔指定要跳过的标签号来跳过已保留的标签或存在间隔的标签。接下来的字段将从下一个数字开始顺序标记。
use prost;
use prost::{Enumeration, Message};
#[derive(Clone, PartialEq, Message)]
struct Person {
#[prost(string, tag = "1")]
pub id: String, // tag=1
// NOTE: Old "name" field has been removed
// pub name: String, // tag=2 (Removed)
#[prost(string, tag = "6")]
pub given_name: String, // tag=6
#[prost(string)]
pub family_name: String, // tag=7
#[prost(string)]
pub formatted_name: String, // tag=8
#[prost(uint32, tag = "3")]
pub age: u32, // tag=3
#[prost(uint32)]
pub height: u32, // tag=4
#[prost(enumeration = "Gender")]
pub gender: i32, // tag=5
// NOTE: Skip to less commonly occurring fields
#[prost(string, tag = "16")]
pub name_prefix: String, // tag=16 (eg. mr/mrs/ms)
#[prost(string)]
pub name_suffix: String, // tag=17 (eg. jr/esq)
#[prost(string)]
pub maiden_name: String, // tag=18
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Enumeration)]
pub enum Gender {
Unknown = 0,
Female = 1,
Male = 2,
}
Nix
prost项目维护了本地开发中的flakes支持。一旦您设置了nix和nix flakes,只需运行nix develop
即可获得配置了所需依赖项的shell,以编译整个项目。
常见问题解答
- 能否将
prost
实现为Serde
的序列化器?
可能不行,但我愿意听取Serde专家的意见。尝试使用Serde序列化Protobuf消息有两个复杂性:
- Protobuf字段需要一个编号标签,而目前似乎在
serde
中没有适合此目的的机制。 - Protobuf类型到Rust类型的映射不是一对一的。因此,基于特质的调度方法不太适用。例如:六种不同的Protobuf字段类型对应于Rust中的
Vec<i32>
:repeated int32
、repeated sint32
、repeated sfixed32
及其压缩版本。
但是,可以将 serde
derive 标签放置到生成的类型上,因此相同的结构可以同时支持 prost
和 Serde
。
- 在 MacOS 上尝试运行
cargo test
时出现错误
如果错误是关于缺少 autoreconf
或类似问题,您可能可以通过运行以下命令来解决它们
brew install automake
brew install libtool
许可证
prost
根据 Apache 许可证(版本 2.0)进行分发。
有关详细信息,请参阅 LICENSE。
版权所有 2022 Dan Burkert & Tokio 贡献者
依赖关系
约 2MB
约 48K SLoC