1个不稳定版本

0.5.0 2019年12月27日

#2070编码

Apache-2.0

81KB
2K SLoC

Build Status Windows Build Status Documentation Crate

PROST!

prostProtocol BuffersRust 语言 实现。 prostproto2proto3 文件生成简单、惯用和可读的 Rust 代码。

与其他 Protocol Buffers 实现相比,prost

  • 通过利用 Rust derive 属性,生成简单、惯用和可读的 Rust 类型。
  • 保留来自 .proto 文件的注释。
  • 允许通过添加属性来序列化和反序列化现有 Rust 类型(非从 .proto 生成)。
  • 使用 bytes::{Buf, BufMut} 抽象进行序列化,而不是使用 std::io::{Read, Write}
  • 在将生成的代码组织到 Rust 模块时,尊重 Protobuf package 指定。
  • 在反序列化过程中保留未知枚举值。
  • 不支持运行时反射或消息描述符。

在 Cargo 项目中使用 prost

首先,将 prost 及其公开依赖项添加到您的 Cargo.toml 中(有关当前版本,请参阅 crates.io

[dependencies]
prost = <prost-version>
bytes = <bytes-version>
# Only necessary if using Protobuf well-known types:
prost-types = <prost-version>

向 Cargo 项目添加 .proto 编译推荐的方式是使用 prost-build 库。有关更多详细信息和方法示例,请参阅 prost-build 文档

生成的代码

prost 从源 .proto 文件使用 proto2proto3 语法生成 Rust 代码。 prost 的目标是使生成的代码尽可能简单。

所有与 prost 一起使用的 .proto 文件必须包含一个 package 指定器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 类型,并提供辅助方法将 i32 值转换为枚举类型。枚举类型不直接用作字段,因为 Protobuf 规范要求枚举值是 '开放' 的,并且必须可能对未识别的枚举值进行解码。

字段修饰符

Protobuf 标量值和枚举消息字段可以有一个修饰符,这取决于 Protobuf 版本。修饰符更改 Rust 字段的相应类型

.proto 版本 修饰符 Rust 类型
proto2 optional Option<T>
proto2 required T
proto3 default T
proto2/proto3 repeated Vec<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, Debug, PartialEq, Message)]
pub struct Person {
    #[prost(string, tag="1")]
    pub name: String,
    /// Unique ID number for this person.
    #[prost(int32, tag="2")]
    pub id: i32,
    #[prost(string, tag="3")]
    pub email: String,
    #[prost(message, repeated, tag="4")]
    pub phones: Vec<person::PhoneNumber>,
}
pub mod person {
    #[derive(Clone, Debug, PartialEq, Message)]
    pub struct PhoneNumber {
        #[prost(string, tag="1")]
        pub number: String,
        #[prost(enumeration="PhoneType", tag="2")]
        pub type_: i32,
    }
    #[derive(Clone, Copy, Debug, PartialEq, Eq, Enumeration)]
    pub enum PhoneType {
        Mobile = 0,
        Home = 1,
        Work = 2,
    }
}
/// Our address book file is just one of these.
#[derive(Clone, Debug, PartialEq, Message)]
pub struct AddressBook {
    #[prost(message, repeated, tag="1")]
    pub people: Vec<Person>,
}

序列化现有类型

prost 使用自定义 derive 宏来处理类型编码和解码,这意味着如果您的现有Rust类型与Protobuf类型兼容,您可以通过添加适当的 derive 和字段注解来序列化和反序列化它。

目前,添加注解的最佳文档是查看上面的生成代码示例。

现有类型的标签推断

Prost会自动为结构体推断标签。

字段按照指定的顺序进行标签,从 1 开始。

您可以通过在间隔后第一个字段上的 tag 属性中指定要跳过的标签号来跳过已保留的标签或标签值之间有间隙的标签。后续字段将从下一个数字开始进行标签。

#[derive(Clone, Debug, PartialEq, Message)]
struct Person {
  pub id: String, // tag=1

  // NOTE: Old "name" field has been removed
  // pub name: String, // tag=2 (Removed)

  #[prost(tag="6")]
  pub given_name: String, // tag=6
  pub family_name: String, // tag=7
  pub formatted_name: String, // tag=8

  #[prost(tag="3")]
  pub age: u32, // tag=3
  pub height: u32, // tag=4
  #[prost(enumeration="Gender")]
  pub gender: i32, // tag=5

  // NOTE: Skip to less commonly occurring fields
  #[prost(tag="16")]
  pub name_prefix: String, // tag=16  (eg. mr/mrs/ms)
  pub name_suffix: String, // tag=17  (eg. jr/esq)
  pub maiden_name: String, // tag=18
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Enumeration)]
pub enum Gender {
  Unknown = 0,
  Female = 1,
  Male = 2,
}

常见问题解答

  1. prost 可以实现为 Serde 的序列化器吗?

可能不行,但我愿意听取 Serde 专家对此事的看法。尝试使用 Serde 序列化 Protobuf 消息有两个复杂点

  • Protobuf 字段需要一个编号的标签,而目前 serde 中似乎没有适合此目的的机制。
  • Protobuf 类型到 Rust 类型的映射不是 1 对 1 的。因此,基于 trait 的调度方法不太适用。例如:六种不同的 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

版权所有 2017 Dan Burkert

依赖关系

~6MB
~132K SLoC