Rust 代码生成指南

描述协议缓冲区编译器为任何给定协议定义生成的消息对象的 API。

此页面准确描述了协议缓冲区编译器为任何给定协议定义生成的 Rust 代码。

proto2 和 proto3 生成的代码之间的任何差异都将突出显示。在阅读本文档之前,您应该阅读 proto2 语言指南 和/或 proto3 语言指南

Protobuf Rust

Protobuf Rust 是协议缓冲区的一种实现,旨在能够位于我们称为“内核”的其他现有协议缓冲区实现的顶层。

支持多个非 Rust 内核的决定对我们的公共 API 产生了重大影响,包括选择使用像 ProtoStr 这样的自定义类型而不是像 str 这样的 Rust std 类型。有关此主题的更多信息,请参阅 Rust Proto 设计决策

生成的文件名

每个 rust_proto_library 都将编译为一个 crate。最重要的是,对于相应 proto_librarysrcs 中的每个 .proto 文件,都会发出一个 Rust 文件,并且所有这些文件构成一个单独的 crate。

编译器生成的文件因内核而异。通常,输出文件的名称是通过获取 .proto 文件的名称并替换扩展名来计算的。

生成的文件

  • C++ 内核
    • .c.pb.rs - 生成的 Rust 代码
    • .pb.thunks.cc - 生成的 C++ thunk(Rust 代码调用的粘合代码,并委托给 C++ Protobuf API)。
  • C++ Lite 内核
    • <与 C++ 内核相同>
  • UPB 内核
    • .u.pb.rs - 生成的 Rust 代码。
      (但是,rust_proto_library 依赖于 upb_proto_aspect 生成的 .thunks.c 文件。)

如果 proto_library 包含多个文件,则第一个文件被声明为“主”文件,并被视为 crate 的入口点;该文件将包含与 .proto 文件对应的 gencode,以及为与所有“辅助”文件对应的文件中定义的所有符号重新导出。

与大多数其他语言不同,.proto 文件中的 package 声明在 Rust 代码生成中未使用。相反,每个 rust_proto_library(name = "some_rust_proto") 目标都会发出一个名为 some_rust_proto 的 crate,其中包含目标中所有 .proto 文件的生成代码。

消息

给定消息声明

message Foo {}

编译器生成一个名为 Foo 的 struct。Foo struct 定义了以下方法

  • fn new() -> Self:创建 Foo 的新实例。
  • fn parse(data: &[u8]) -> Result<Self, protobuf::ParseError>:如果 data 包含 Foo 的有效 wire format 表示形式,则将 data 解析为 Foo 的实例。否则,该函数返回错误。
  • fn clear_and_parse(&mut self, data: &[u8]) -> Result<(), ParseError>:类似于按顺序调用 .clear()parse()
  • fn serialize(&self) -> Result<Vec<u8>, SerializeError>:将消息序列化为 Protobuf wire format。序列化可能会失败,但很少会失败。失败原因包括超出最大消息大小、内存不足以及未设置的必需字段 (proto2)。
  • fn merge_from(&mut self, other):将 selfother 合并。
  • fn as_view(&self) -> FooView<'_>:返回 Foo 的不可变句柄(view)。这在关于代理类型的章节中进一步介绍。
  • fn as_mut(&mut self) -> FooMut<'_>:返回 Foo 的可变句柄(mut)。这在关于代理类型的章节中进一步介绍。

Foo 实现了以下 trait

  • std::fmt::Debug
  • std::default::Default
  • std::clone::Clone
  • std::ops::Drop
  • std::marker::Send
  • std::marker::Sync

消息代理类型

由于需要支持具有单个 Rust API 的多个内核,因此在某些情况下我们不能使用原生 Rust 引用(&T&mut T),而是需要使用类型(ViewMut)来表达这些概念。这些情况是共享和可变引用到

  • 消息
  • 重复字段
  • Map 字段

例如,编译器会同时发出 struct FooView<'a>FooMut<'msg> 以及 Foo。这些类型用于代替 &Foo&mut Foo,并且它们在借用检查器行为方面与原生 Rust 引用行为相同。就像原生借用一样,View 是 Copy,并且借用检查器将强制您在给定时间拥有任意数量的 View 或最多一个 Mut。

为了本文档的目的,我们专注于描述为拥有的消息类型 (Foo) 发出的所有方法。带有 &self 接收器的这些函数的子集也将包含在 FooView<'msg> 上。带有 &self&mut self 的这些函数的子集也将包含在 FooMut<'msg> 上。

要从 View / Mut 类型创建拥有的消息类型,请调用 to_owned(),这将创建一个深拷贝。

嵌套类型

给定消息声明

message Foo {
  message Bar {
      enum Baz { ... }
  }
}

除了名为 Foo 的 struct 之外,还创建了一个名为 foo 的模块来包含 Bar 的 struct。类似地,一个名为 bar 的嵌套模块来包含深度嵌套的枚举 Baz

pub struct Foo {}

pub mod foo {
   pub struct Bar {}
   pub mod bar {
      pub struct Baz { ... }
   }
}

字段

除了上一节中描述的方法之外,协议缓冲区编译器还为 .proto 文件中消息内定义的每个字段生成一组访问器方法。

按照 Rust 风格,这些方法采用小写/蛇形命名法,例如 has_foo()clear_foo()。请注意,访问器字段名称部分的 capitalization 保留了原始 .proto 文件中的风格,根据 .proto 文件风格指南,该风格应为小写/蛇形命名法。

可选数字字段 (proto2 和 proto3)

对于以下任一字段定义

optional int32 foo = 1;
required int32 foo = 1;

编译器将生成以下访问器方法

  • fn has_foo(&self) -> bool:如果字段已设置,则返回 true
  • fn foo(&self) -> i32:返回字段的当前值。如果未设置字段,则返回默认值。
  • fn foo_opt(&self) -> protobuf::Optional<i32>:如果字段已设置,则返回带有变体 Set(value) 的 optional,如果未设置,则返回带有 Unset(default value) 的 optional。
  • fn set_foo(&mut self, val: i32):设置字段的值。调用此函数后,has_foo() 将返回 truefoo() 将返回 value
  • fn clear_foo(&mut self):清除字段的值。调用此函数后,has_foo() 将返回 falsefoo() 将返回默认值。

对于其他数字字段类型(包括 bool),int32 将根据 标量值类型表 替换为相应的 C++ 类型。

隐式存在性数字字段 (proto3)

对于这些字段定义

int32 foo = 1;
  • fn foo(&self) -> i32:返回字段的当前值。如果未设置字段,则返回 0
  • fn set_foo(&mut self, val: i32):设置字段的值。调用此函数后,foo() 将返回 value。

对于其他数字字段类型(包括 bool),int32 将根据 标量值类型表 替换为相应的 C++ 类型。

可选字符串/字节字段 (proto2 和 proto3)

对于以下任一字段定义

optional string foo = 1;
required string foo = 1;
optional bytes foo = 1;
required bytes foo = 1;

编译器将生成以下访问器方法

  • fn has_foo(&self) -> bool:如果字段已设置,则返回 true
  • fn foo(&self) -> &protobuf::ProtoStr:返回字段的当前值。如果未设置字段,则返回默认值。
  • fn foo_opt(&self) -> protobuf::Optional<&ProtoStr>:如果字段已设置,则返回带有变体 Set(value) 的 optional,如果未设置,则返回带有 Unset(default value) 的 optional。
  • fn clear_foo(&mut self):清除字段的值。调用此函数后,has_foo() 将返回 falsefoo() 将返回默认值。

对于 bytes 类型的字段,编译器将生成 ProtoBytes 类型。

隐式存在性字符串/字节字段 (proto3)

对于这些字段定义

optional string foo = 1;
string foo = 1;
optional bytes foo = 1;
bytes foo = 1;

编译器将生成以下访问器方法

  • fn foo(&self) -> &ProtoStr:返回字段的当前值。如果未设置字段,则返回空字符串/空字节。
  • fn foo_opt(&self) -> Optional<&ProtoStr>:如果字段已设置,则返回带有变体 Set(value) 的 optional,如果未设置,则返回带有 Unset(default value) 的 optional。
  • fn set_foo(&mut self, value: IntoProxied<ProtoString>):将字段设置为 value。调用此函数后,foo() 将返回 valuehas_foo() 将返回 true
  • fn has_foo(&self) -> bool:如果字段已设置,则返回 true
  • fn clear_foo(&mut self):清除字段的值。调用此函数后,has_foo() 将返回 falsefoo() 将返回默认值。

对于 bytes 类型的字段,编译器将生成 ProtoBytes 类型。

支持 Cord 的单数字符串和字节字段

[ctype = CORD] 使字节和字符串能够作为 absl::Cord 存储在 C++ Protobufs 中。absl::Cord 目前在 Rust 中没有等效类型。Protobuf Rust 使用枚举来表示 cord 字段

enum ProtoStringCow<'a> {
  Owned(ProtoString),
  Borrowed(&'a ProtoStr)
}

在常见情况下,对于小字符串,absl::Cord 将其数据存储为连续字符串。在这种情况下,cord 访问器返回 ProtoStringCow::Borrowed。如果底层 absl::Cord 是非连续的,则访问器将数据从 cord 复制到拥有的 ProtoString 并返回 ProtoStringCow::OwnedProtoStringCow 实现了 Deref<Target=ProtoStr>

对于以下任一字段定义

optional string foo = 1 [ctype = CORD];
string foo = 1 [ctype = CORD];
optional bytes foo = 1 [ctype = CORD];
bytes foo = 1 [ctype = CORD];

编译器生成以下访问器方法

  • fn my_field(&self) -> ProtoStringCow<'_>:返回字段的当前值。如果未设置字段,则返回空字符串/空字节。
  • fn set_my_field(&mut self, value: IntoProxied<ProtoString>):将字段设置为 value。调用此函数后,foo() 返回 valuehas_foo() 返回 true
  • fn has_foo(&self) -> bool:如果字段已设置,则返回 true
  • fn clear_foo(&mut self):清除字段的值。调用此函数后,has_foo() 返回 falsefoo() 返回默认值。Cord 尚未实现。

对于 bytes 类型的字段,编译器生成 ProtoBytesCow 类型。

可选枚举字段 (proto2 和 proto3)

给定枚举类型

enum Bar {
  BAR_UNSPECIFIED = 0;
  BAR_VALUE = 1;
  BAR_OTHER_VALUE = 2;
}

编译器生成一个 struct,其中每个变体都是一个关联常量

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Bar(i32);

impl Bar {
  pub const Unspecified: Bar = Bar(0);
  pub const Value: Bar = Bar(1);
  pub const OtherValue: Bar = Bar(2);
}

对于以下任一字段定义

optional Bar foo = 1;
required Bar foo = 1;

编译器将生成以下访问器方法

  • fn has_foo(&self) -> bool:如果字段已设置,则返回 true
  • fn foo(&self) -> Bar:返回字段的当前值。如果未设置字段,则返回默认值。
  • fn foo_opt(&self) -> Optional<Bar>:如果字段已设置,则返回带有变体 Set(value) 的 optional,如果未设置,则返回带有 Unset(default value) 的 optional。
  • fn set_foo(&mut self, val: Bar):设置字段的值。调用此函数后,has_foo() 将返回 truefoo() 将返回 value
  • fn clear_foo(&mut self):清除字段的值。调用此函数后,has_foo() 将返回 false,foo() 将返回默认值。

隐式存在性枚举字段 (proto3)

给定枚举类型

enum Bar {
  BAR_UNSPECIFIED = 0;
  BAR_VALUE = 1;
  BAR_OTHER_VALUE = 2;
}

编译器生成一个 struct,其中每个变体都是一个关联常量

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Bar(i32);

impl Bar {
  pub const Unspecified: Bar = Bar(0);
  pub const Value: Bar = Bar(1);
  pub const OtherValue: Bar = Bar(2);
}

对于这些字段定义

Bar foo = 1;

编译器将生成以下访问器方法

  • fn foo(&self) -> Bar:返回字段的当前值。如果未设置字段,则返回默认值。
  • fn set_foo(&mut self, value: Bar):设置字段的值。调用此函数后,has_foo() 将返回 truefoo() 将返回 value

可选嵌入消息字段 (proto2 和 proto3)

给定消息类型

message Bar {}

对于以下任一字段定义

//proto2
optional Bar foo = 1;

//proto3
Bar foo = 1;
optional Bar foo = 1;

编译器将生成以下访问器方法

  • fn foo(&self) -> BarView<'_>:返回字段当前值的 view。如果未设置字段,则返回空消息。
  • fn foo_mut(&mut self) -> BarMut<'_>:返回字段当前值的可变句柄。如果未设置字段,则设置该字段。调用此方法后,has_foo() 返回 true。
  • fn foo_opt(&self) -> protobuf::Optional<BarView>:如果字段已设置,则返回带有其 value 的变体 Set。否则,返回带有默认值的变体 Unset
  • fn set_foo(&mut self, value: impl protobuf::IntoProxied<Bar>):将字段设置为 value。调用此方法后,has_foo() 返回 true
  • fn has_foo(&self) -> bool:如果字段已设置,则返回 true
  • fn clear_foo(&mut self):清除字段。调用此方法后,has_foo() 返回 false

重复字段

对于任何重复字段定义,编译器将生成相同的三种访问器方法,这些方法仅在字段类型上有所不同。

例如,给定以下字段定义

repeated int32 foo = 1;

编译器将生成以下访问器方法

  • fn foo(&self) -> RepeatedView<'_, i32>:返回底层重复字段的 view。
  • fn foo_mut(&mut self) -> RepeatedMut<'_, i32>:返回底层重复字段的可变句柄。
  • fn set_foo(&mut self, src: impl IntoProxied<Repeated<i32>>):将底层重复字段设置为 src 中提供的新重复字段。

对于不同的字段类型,只有 RepeatedViewRepeatedMutRepeated 类型的相应泛型类型会发生变化。例如,给定 string 类型的字段,foo() 访问器将返回 RepeatedView<'_, ProtoString>

Map 字段

对于此 map 字段定义

map<int32, int32> weight = 1;

编译器将生成以下 3 个访问器方法

  • fn weight(&self) -> protobuf::MapView<'_, i32, i32>:返回底层 map 的不可变 view。
  • fn weight_mut(&mut self) -> protobuf::MapMut<'_, i32, i32>:返回底层 map 的可变句柄。
  • fn set_weight(&mut self, src: protobuf::IntoProxied<Map<i32, i32>>):将底层 map 设置为 src

对于不同的字段类型,只有 MapViewMapMutMap 类型的相应泛型类型会发生变化。例如,给定 string 类型的字段,foo() 访问器将返回 MapView<'_, int32, ProtoString>

Any

Rust Protobuf 目前没有特殊处理 Any;它的行为方式就像它是一个具有以下定义的简单消息

message Any {
  string type_url = 1;
  bytes value = 2  [ctype = CORD];
}

Oneof

给定像这样的 oneof 定义

oneof example_name {
    int32 foo_int = 4;
    string foo_string = 9;
    ...
}

编译器将为每个字段生成访问器(getter、setter、hazer),就像在 oneof 之外将同一字段声明为 optional 字段一样。因此,您可以像处理常规字段一样处理 oneof 字段,但是设置一个字段将清除 oneof 块中的其他字段。此外,还为 oneof 块发出以下类型

  #[non_exhaustive]
  #[derive(Debug, Clone, Copy)]

  pub enum ExampleNameOneof<'msg> {
    FooInt(i32) = 4,
    FooString(&'msg protobuf::ProtoStr) = 9,
    not_set(std::marker::PhantomData<&'msg ()>) = 0
  }
  #[derive(Debug, Copy, Clone, PartialEq, Eq)]

  pub enum ExampleNameCase {
    FooInt = 4,
    FooString = 9,
    not_set = 0
  }

此外,它将生成两个访问器

  • fn example_name(&self) -> ExampleNameOneof<_>:返回枚举变体,指示设置了哪个字段以及该字段的值。如果未设置任何字段,则返回 not_set
  • fn example_name_case(&self) -> ExampleNameCase:返回枚举变体,指示设置了哪个字段。如果未设置任何字段,则返回 not_set

枚举

给定像这样的枚举定义

enum FooBar {
  FOO_BAR_UNKNOWN = 0;
  FOO_BAR_A = 1;
  FOO_B = 5;
  VALUE_C = 1234;
}

编译器将生成

  #[derive(Clone, Copy, PartialEq, Eq, Hash)]
  #[repr(transparent)]
  pub struct FooBar(i32);

  impl FooBar {
    pub const Unknown: FooBar = FooBar(0);
    pub const A: FooBar = FooBar(1);
    pub const FooB: FooBar = FooBar(5);
    pub const ValueC: FooBar = FooBar(1234);
  }

请注意,对于具有与枚举匹配的前缀的值,前缀将被剥离;这样做是为了提高人体工程学。枚举值通常以枚举名称为前缀,以避免同级枚举之间的名称冲突(这遵循 C++ 枚举的语义,其中值不受其包含枚举的范围限制)。由于生成的 Rust const 位于 impl 中,因此在 .proto 文件中添加的额外前缀在 Rust 中将是冗余的。

扩展 (仅限 proto2)

扩展的 Rust API 目前正在开发中。扩展字段将通过 parse/serialize 进行维护,并且在 C++ 互操作的情况下,如果从 Rust 访问消息,则设置的任何扩展都将保留(并在消息复制或合并的情况下传播)。

Arena 分配

尚未实现 Arena 分配消息的 Rust API。

在内部,upb 内核上的 Protobuf Rust 使用 arena,但在 C++ 内核上则不使用。但是,对 C++ 中 arena 分配的消息的引用(const 和 mutable)可以安全地传递给 Rust 以进行访问或修改。

服务

尚未实现服务的 Rust API。