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

已删除 muta-vendor-prost

为 Rust 语言实现的 Protocol Buffers

0.5.1 2019 年 12 月 31 日
0.5.0 2019 年 12 月 31 日

#69 in #prost

Apache-2.0

89KB
2K SLoC

Build Status Windows Build Status Documentation Crate

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 中(有关当前版本,请参阅 crates.io

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

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

生成的代码

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

使用 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 消息中的字段被翻译为对应类型的公共结构体字段。

标量值

标量值类型按以下方式转换

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 类型的映射不是一对一的。因此,基于特性的调度方法效果不佳。例如:六种不同的 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