#serialization #protobuf #buffer #prost #fields #protocols #message

prost-bytes05

Rust语言的Protocol Buffers实现

2个版本

0.0.2 2019年12月27日
0.0.1 2019年12月27日

#1790 in 编码

Apache-2.0

81KB
2K SLoC

Build Status Windows Build Status Documentation Crate

PROST!

prostProtocol Buffers 的 Rust 语言实现。 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 使用 proto2proto3 语法从源 .proto 文件生成 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 值转换为枚举类型的方法助手。该 enum 类型不直接用作字段,因为 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 的。因此,基于特质的方法进行调度效果不佳。例如:六种不同的 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

依赖项

~6.5MB
~132K SLoC