1 个不稳定版本

0.11.3 2024年4月28日

#544 in 编码

Apache-2.0

110KB
2.5K SLoC

continuous integration Documentation Crate Dependency Status Discord

PROST!

prostProtocol Buffers 的 Rust 语言实现。 prostproto2proto3 文件生成简单的、惯用的 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 使用 proto2proto3 语法从源 .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],具有 typeset_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

prostno_std Crates兼容。要启用no_std支持,请在prostprost-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,以编译整个项目。

常见问题解答

  1. 能否将prost实现为Serde的序列化器?

可能不行,但我愿意听取Serde专家的意见。尝试使用Serde序列化Protobuf消息有两个复杂性:

  • Protobuf字段需要一个编号标签,而目前似乎在serde中没有适合此目的的机制。
  • Protobuf类型到Rust类型的映射不是一对一的。因此,基于特质的调度方法不太适用。例如:六种不同的Protobuf字段类型对应于Rust中的Vec<i32>repeated int32repeated sint32repeated sfixed32及其压缩版本。

但是,可以将 serde derive 标签放置到生成的类型上,因此相同的结构可以同时支持 prostSerde

  1. 在 MacOS 上尝试运行 cargo test 时出现错误

如果错误是关于缺少 autoreconf 或类似问题,您可能可以通过运行以下命令来解决它们

brew install automake
brew install libtool

许可证

prost 根据 Apache 许可证(版本 2.0)进行分发。

有关详细信息,请参阅 LICENSE

版权所有 2022 Dan Burkert & Tokio 贡献者

依赖关系

约 2MB
约 48K SLoC