34次发布

0.13.1 2024年7月9日
0.12.6 2024年5月17日
0.12.3 2023年11月21日
0.11.9 2023年4月12日
0.1.0 2017年6月25日

3 in 编码

Download history 1092795/week @ 2024-05-03 1201541/week @ 2024-05-10 1260712/week @ 2024-05-17 1257101/week @ 2024-05-24 1433696/week @ 2024-05-31 1446732/week @ 2024-06-07 1329743/week @ 2024-06-14 1409380/week @ 2024-06-21 1319953/week @ 2024-06-28 1407068/week @ 2024-07-05 1415674/week @ 2024-07-12 1458519/week @ 2024-07-19 1504968/week @ 2024-07-26 1483474/week @ 2024-08-02 1582512/week @ 2024-08-09 1262781/week @ 2024-08-16

6,118,275 downloads per month
Used in 3,877 crates (1,898 directly)

Apache-2.0

96KB
2K SLoC

continuous integration Documentation Crate Dependency Status Discord

PROST!

prost is a Protocol Buffers implementation for the Rust Language. prost generates simple, idiomatic Rust code from proto2 and proto3 files.

Compared to other Protocol Buffers implementations, prost

  • Generates simple, idiomatic, and readable Rust types by taking advantage of Rust derive attributes.
  • Retains comments from .proto files in generated Rust code.
  • Allows existing Rust types (not generated from a .proto) to be serialized and deserialized by adding attributes.
  • Uses the bytes::{Buf, BufMut} abstractions for serialization instead of std::io::{Read, Write}.
  • Respects the Protobuf package specifier when organizing generated code into Rust modules.
  • Preserves unknown enum values during deserialization.
  • Does not include support for runtime reflection or message descriptors.

Using prost in a Cargo Project

First, add prost and its public dependencies to your Cargo.toml

[dependencies]
prost = "0.13"
# Only necessary if using Protobuf well-known types:
prost-types = "0.13"

The recommended way to add .proto compilation to a Cargo project is to use the prost-build library. See the prost-build documentation for more details and examples.

See the snazzy repository for a simple start-to-finish example.

MSRV

prost 遵循 tokio-rs 项目的 MSRV 模型并支持 1.70。有关 tokio msrv 政策的更多信息,您可以在此查看:这里

生成的代码

prost 使用 proto2proto3 语法从源 .proto 文件生成 Rust 代码。 prost 的目标是最简化生成的代码。

protoc

prost-build v0.11 版本发布后,调用 compile_protos 时将需要 protoc(除非启用了 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 消息中的字段被转换为相应类型的公共结构体字段。

标量值

标量值类型转换为以下内容

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,
}

[^1]: 为了清晰起见,省略了注释。下面是完整示例。

您可以通过以下方式将 PhoneType 值转换为 i32

PhoneType::Mobile as i32

添加到生成的 PhoneType 中的 #[derive(::prost::Enumeration)] 注释为类型添加了这些关联函数

impl PhoneType {
    pub fn is_valid(value: i32) -> bool { ... }
    #[deprecated]
    pub fn from_i32(value: i32) -> Option<PhoneType> { ... }
}

它还添加了一个 impl TryFrom<i32> for PhoneType 实现方法,因此您可以通过以下方式将 i32 转换为其对应的 PhoneType 值,例如

let phone_type = 2i32;

match PhoneType::try_from(phone_type) {
    Ok(PhoneType::Mobile) => ...,
    Ok(PhoneType::Home) => ...,
    Ok(PhoneType::Work) => ...,
    Err(_) => ...,
}

此外,当在 Message 中将 proto 枚举用作字段时,消息将具有 '访问器' 方法来获取/设置字段的值,该值与 Rust 枚举类型相同。例如,此 proto PhoneNumber 消息包含一个名为 type 的字段,其类型为 PhoneType

message PhoneNumber {
  string number = 1;
  PhoneType type = 2;
}

将变成以下 Rust 类型 [^2],具有 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 规范要求枚举值是 '开放的',并且必须能够解码未识别的枚举值。

[^2]: 为了清晰起见,省略了注解。下面是完整示例。

字段修饰符

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[^3]

pub struct Foo {
    pub widget: Option<foo::Widget>,
}
pub mod foo {
    pub enum Widget {
        Quux(i32),
        Bar(String),
    }
}

oneof 字段总是包装在 Option 中。

[^3]: 为了清晰起见,省略了注解。下面是完整示例。

服务

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>,
}

访问 protocFileDescriptorSet

可以使用 prost_build::Config::file_descriptor_set_path 选项在构建和代码生成步骤中输出文件描述符集。当与 std::include_bytes 宏以及 prost_types::FileDescriptorSet 类型一起使用时,Prost 的应用程序和库可以实现需要从原始 .proto 文件详细信息的反射能力。

no_std Crate 中使用 prost

prostno_std Crate 兼容。要启用 no_std 支持,请在 prostprost-types 中禁用 std 功能。

[dependencies]
prost = { version = "0.13.1", default-features = false, features = ["prost-derive"] }
# Only necessary if using Protobuf well-known types:
prost-types = { version = "0.13.1", default-features = false }

此外,在 build.rs 中配置 prost-build 以输出 BTreeMap 而不是 HashMap,以处理所有 Protobuf map 字段。

let mut config = prost_build::Config::new();
config.btree_map(&["."]);

当使用 edition 2015 时,可能需要在包含 prost-生成的代码的 crate 中添加一个 extern crate core; 指令。

序列化现有类型

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

目前,关于添加注释的最好文档是查看上面的生成代码示例。

现有类型的 Tag 推断

Prost 自动推断结构体的 Tag。

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

您可以通过在间隔后的第一个字段上指定 tag 属性来跳过已保留的标签或存在间隔的连续标签值。接下来的字段将从下一个数字开始顺序标记。

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,以编译整个项目。

功能标志

  • std:启用与标准库的集成。为 no_std 支持禁用此功能。默认启用此功能。
  • derive:启用与 prost-derive 的集成。禁用此功能以减少编译时间。默认启用此功能。
  • prost-derive:已弃用。是 derive 功能的别名。
  • no-recursion-limit:禁用递归限制。递归限制为 100,无法自定义。

常见问题解答

  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贡献者

依赖项

~175KB