#protobuf #prost #serialization #buffer #message #tags #field

已删除 tmkms-prost

Rust语言协议缓冲区实现

使用旧的Rust 2015

0.0.1 2018年10月16日

#74#prost

Apache-2.0

72KB
1.5K SLoC

Build Status Windows Build Status Documentation Crate

PROST!

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

与其他协议缓冲区实现相比,prost

  • 通过利用 Rust 的 derive 属性,生成简单、惯用和易于阅读的 Rust 类型。
  • 在生成的 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>
prost-derive = <prost-version>
# Only necessary if using Protobuf well-known types:
prost-types = <prost-version>
bytes = <bytes-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 类型的映射不是一对一的。因此,基于特质的调度方法不太适用。例如:六种不同的 Protobuf 字段类型对应 Rust 中的 Vec<i32>repeated int32repeated sint32repeated sfixed32 以及它们的压缩版本。

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

许可证

prost 在 Apache 许可证(版本 2.0)的条款下分发。

有关详细信息,请参阅 LICENSE

版权所有 2017 Dan Burkert

依赖项

~390KB