Rust 生成代码指南
本页面精确描述了协议缓冲区编译器针对任何给定协议定义生成的 Rust 代码。
本文档涵盖了协议缓冲区编译器如何为 proto2、proto3 和 protobuf 版本生成 Rust 代码。并突出显示了 proto2、proto3 和版本生成代码之间的任何差异。在阅读本文档之前,您应该阅读 proto2 语言指南、proto3 语言指南 或 版本指南。
Protobuf Rust
Protobuf Rust 是 Protocol Buffers 的一个实现,旨在能够基于我们称之为“内核”的其他现有 Protocol Buffers 实现之上运行。
支持多个非 Rust 内核的决定显著影响了我们的公共 API,包括选择使用自定义类型如 ProtoStr 而非 Rust std 类型如 str。有关此主题的更多信息,请参阅 Rust Proto 设计决策。
包(Packages)
与大多数其他语言不同,.proto 文件中的 package 声明未在 Rust 代码生成中使用。
当使用 rust_proto_library 时,该库将对应一个 crate。目标名称用作 crate 名称。请相应地选择您的库名称。
使用 Cargo 时,我们建议您将 generated.rs 入口点 include! 到一个适当名称的模块中,如我们的 示例 Crate 中所示。
消息
给定消息声明
message Foo {}
编译器生成一个名为 Foo 的结构体。Foo 结构体定义了以下关联函数和方法
关联函数
fn new() -> Self: 创建Foo的新实例。
特性(Traits)
由于多种原因,包括 gencode 大小、名称冲突问题和 gencode 稳定性,消息上的大多数常见功能都在特性(traits)上实现,而不是作为固有实现。
大多数用户应该导入我们的 prelude,它只包含特性和我们的 proto! 宏,不包含其他类型(use protobuf::prelude::*)。如果您想避免使用 prelude,可以根据需要导入特定的特性(请参阅
此处文档,了解特性的名称和定义,如果您想直接导入它们)。
fn parse(data: &[u8]) -> Result: 解析消息的新实例。fn parse_dont_enforce_required(data: &[u8]) -> Result: 与parse相同,但不因 proto2required字段缺失而失败。fn clear(&mut self): 清除消息。fn clear_and_parse(&mut self, data: &[u8]) -> Result<(), ParseError>: 清除并解析到现有实例中。fn clear_and_parse_dont_enforce_required(&mut self, data: &[u8]) -> Result<(), ParseError>: 与parse相同,但不因 proto2required字段缺失而失败。fn serialize(&self) -> Result: 将消息序列化为 Protobuf 线格式。序列化可能会失败,但很少发生。失败原因包括表示超出最大编码消息大小(必须小于 2 GiB),以及未设置的, SerializeError> required字段 (proto2)。fn take_from(&mut self, other): 将other移动到self中,丢弃self包含的任何先前状态。fn copy_from(&mut self, other): 将other复制到self中,丢弃self包含的任何先前状态。other未修改。fn merge_from(&mut self, other): 将other合并到self中。fn as_view(&self) -> FooView<'_>: 返回Foo的不可变句柄(view)。这将在代理类型部分进一步介绍。fn as_mut(&mut self) -> FooMut<'_>: 返回Foo的可变句柄(mut)。这将在代理类型部分进一步介绍。
Foo 额外实现了以下标准特性
std::fmt::Debugstd::default::Defaultstd::clone::Clonestd::marker::Sendstd::marker::Sync
流畅地创建新实例
setter 的 API 设计遵循我们既定的 Protobuf 惯用法,但在构建新实例时,某些其他语言的冗长是一个轻微的痛点。为了缓解这种情况,我们提供了 proto! 宏,它可以更简洁/流畅地创建新实例。
例如,无需这样编写
let mut msg = SomeMsg::new();
msg.set_x(1);
msg.set_y("hello");
msg.some_submessage_mut().set_z(42);
这个宏可以用来这样编写
let msg = proto!(SomeMsg {
x: 1,
y: "hello",
some_submsg: SomeSubmsg {
z: 42
}
});
消息代理类型
由于一些技术原因,我们选择在某些情况下避免使用原生 Rust 引用(&T 和 &mut T)。相反,我们需要使用类型——View 和 Mut 来表达这些概念。这些情况是共享的和可变的引用到
- 消息
- 重复字段
- Map 字段
例如,编译器会与 Foo 一起发出结构体 FooView<'a> 和 FooMut<'msg>。这些类型用于代替 &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 的结构体之外,还创建了一个名为 foo 的模块来包含 Bar 的结构体。类似地,创建了一个名为 bar 的嵌套模块来包含深度嵌套的枚举 Baz
pub struct Foo {}
pub mod foo {
pub struct Bar {}
pub mod bar {
pub struct Baz { ... }
}
}
字段
除了上一节中描述的方法之外,协议缓冲区编译器还会为 .proto 文件中消息内定义的每个字段生成一组访问器方法。
按照 Rust 风格,方法使用小写/蛇形命名,例如 has_foo() 和 clear_foo()。请注意,访问器中字段名称部分的字母大小写保持与原始 .proto 文件中的风格一致,而根据 .proto 文件风格指南,它应该是小写/蛇形命名。
具有显式存在性的字段
显式存在性意味着字段区分默认值和未设置值。在 proto2 中,optional 字段具有显式存在性。在 proto3 中,只有消息字段以及 oneof 或 optional 字段具有显式存在性。存在性通过在版本中设置 features.field_presence 选项来设置。
数字字段
对于此字段定义:
int32 foo = 1;
编译器生成以下访问器方法
fn has_foo(&self) -> bool: 如果字段已设置,则返回true。fn foo(&self) -> i32: 返回字段的当前值。如果字段未设置,则返回默认值。- fn foo_opt(&self) -> protobuf::Optional
: 如果字段已设置,则返回带有变体Set(value)的可选项;如果未设置,则返回Unset(default value)的可选项。请参阅 [`Optional` rustdoc](https://docs.rs/protobuf/4.33.5-release/protobuf/enum.Optional.html) fn set_foo(&mut self, val: i32): 设置字段的值。调用此方法后,has_foo()将返回true,foo()将返回value。fn clear_foo(&mut self): 清除字段的值。调用此方法后,has_foo()将返回false,foo()将返回默认值。
对于其他数字字段类型(包括 bool),int32 将根据 标量值类型表 替换为相应的 Rust 类型。
字符串和字节字段
对于这些字段定义
string foo = 1;
bytes foo = 1;
编译器生成以下访问器方法
fn has_foo(&self) -> bool: 如果字段已设置,则返回true。fn foo(&self) -> &protobuf::ProtoStr: 返回字段的当前值。如果字段未设置,则返回默认值。参见ProtoStrrustdoc。fn foo_opt(&self) -> protobuf::Optional<&ProtoStr>: 如果字段已设置,则返回带有变体Set(value)的可选项;如果未设置,则返回Unset(default value)的可选项。fn set_foo(&mut self, val: impl IntoProxied: 设置字段的值。) &str、String、&ProtoStr和ProtoString都实现了IntoProxied,并且可以传递给此方法。fn clear_foo(&mut self): 清除字段的值。调用此方法后,has_foo()将返回false,foo()将返回默认值。
对于 bytes 类型的字段,编译器将生成 ProtoBytes 类型。
枚举字段
给定任何 proto 语法版本中的此枚举定义
enum Bar {
BAR_UNSPECIFIED = 0;
BAR_VALUE = 1;
BAR_OTHER_VALUE = 2;
}
编译器生成一个结构体,其中每个变体都是一个关联常量
#[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 has_foo(&self) -> bool: 如果字段已设置,则返回true。fn foo(&self) -> Bar: 返回字段的当前值。如果字段未设置,则返回默认值。fn foo_opt(&self) -> Optional: 如果字段已设置,则返回带有变体Set(value)的可选项;如果未设置,则返回Unset(default value)的可选项。fn set_foo(&mut self, val: Bar): 设置字段的值。调用此方法后,has_foo()将返回true,foo()将返回value。fn clear_foo(&mut self): 清除字段的值。调用此方法后,has_foo()将返回 false,foo()将返回默认值。
内嵌消息字段
给定任何 proto 语法版本中的消息类型 Bar
message Bar {}
对于任何这些字段定义
message MyMessage {
Bar foo = 1;
}
编译器将生成以下访问器方法
fn foo(&self) -> BarView<'_>: 返回字段当前值的视图。如果字段未设置,则返回一个空消息。fn foo_mut(&mut self) -> BarMut<'_>: 返回字段当前值的可变句柄。如果字段未设置,则设置该字段。调用此方法后,has_foo()返回 true。fn foo_opt(&self) -> protobuf::Optional: 如果字段已设置,则返回带有其value的变体Set。否则返回带有默认值的变体Unset。fn set_foo(&mut self, value: impl protobuf::IntoProxied: 将字段设置为) value。调用此方法后,has_foo()返回true。fn has_foo(&self) -> bool: 如果字段已设置,则返回true。fn clear_foo(&mut self): 清除字段。调用此方法后,has_foo()返回false。
具有隐式存在性的字段(proto3 和 Editions)
隐式存在性意味着字段不区分默认值和未设置值。在 proto3 中,字段默认具有隐式存在性。在版本中,您可以通过将 field_presence 功能设置为 IMPLICIT 来声明具有隐式存在性的字段。
数字字段
对于这些字段定义
// proto3
int32 foo = 1;
// editions
message MyMessage {
int32 foo = 1 [features.field_presence = IMPLICIT];
}
编译器生成以下访问器方法
fn foo(&self) -> i32: 返回字段的当前值。如果字段未设置,则返回0。fn set_foo(&mut self, val: i32): 设置字段的值。
对于其他数字字段类型(包括 bool),int32 将根据 标量值类型表 替换为相应的 Rust 类型。
字符串和字节字段
对于这些字段定义
// proto3
string foo = 1;
bytes foo = 1;
// editions
string foo = 1 [features.field_presence = IMPLICIT];
bytes bar = 2 [features.field_presence = IMPLICIT];
编译器将生成以下访问器方法
fn foo(&self) -> &ProtoStr: 返回字段的当前值。如果字段未设置,则返回空字符串/空字节。参见ProtoStrrustdoc。fn set_foo(&mut self, value: IntoProxied: 将字段设置为) value。
对于 bytes 类型的字段,编译器将生成 ProtoBytes 类型。
支持 Cord 的单字符串和字节字段
[ctype = CORD] 允许字节和字符串以 absl::Cord 的形式存储在 C++ Protobuf 中。absl::Cord 目前在 Rust 中没有等效类型。Protobuf Rust 使用枚举来表示 cord 字段
enum ProtoStringCow<'a> {
Owned(ProtoString),
Borrowed(&'a ProtoStr)
}
在常见情况下,对于小字符串,absl::Cord 将其数据存储为连续字符串。在这种情况下,cord 访问器返回 ProtoStringCow::Borrowed。如果底层 absl::Cord 不连续,访问器将数据从 cord 复制到拥有的 ProtoString 中,并返回 ProtoStringCow::Owned。ProtoStringCow 实现了 Deref。
对于任何这些字段定义
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: 将字段设置为) value。调用此函数后,foo()返回value,has_foo()返回true。fn has_foo(&self) -> bool: 如果字段已设置,则返回true。fn clear_foo(&mut self): 清除字段的值。调用此方法后,has_foo()返回false,foo()返回默认值。Cord 尚未实现。
对于 bytes 类型的字段,编译器将生成 ProtoBytesCow 类型。
编译器生成以下访问器方法
fn foo(&self) -> &ProtoStr: 返回字段的当前值。如果字段未设置,则返回空字符串/空字节。fn set_foo(&mut self, value: impl IntoProxied: 将字段设置为) value。
枚举字段
给定枚举类型
enum Bar {
BAR_UNSPECIFIED = 0;
BAR_VALUE = 1;
BAR_OTHER_VALUE = 2;
}
编译器生成一个结构体,其中每个变体都是一个关联常量
#[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);
}
对于这些字段定义
// proto3
Bar foo = 1;
// editions
message MyMessage {
Bar foo = 1 [features.field_presence = IMPLICIT];
}
编译器将生成以下访问器方法
fn foo(&self) -> Bar: 返回字段的当前值。如果字段未设置,则返回默认值。fn set_foo(&mut self, value: Bar): 设置字段的值。调用此方法后,has_foo()将返回true,foo()将返回value。
重复字段
对于任何重复字段定义,编译器将生成相同的三个访问器方法,这些方法仅在字段类型上有所不同。
在版本中,您可以使用 repeated_field_encoding 功能来控制重复原始字段的线格式编码。
// proto2
repeated int32 foo = 1; // EXPANDED by default
// proto3
repeated int32 foo = 1; // PACKED by default
// editions
repeated int32 foo = 1 [features.repeated_field_encoding = PACKED];
repeated int32 bar = 2 [features.repeated_field_encoding = EXPANDED];
给定上述任何字段定义,编译器都会生成以下访问器方法
fn foo(&self) -> RepeatedView<'_, i32>: 返回底层重复字段的视图。参见RepeatedViewrustdoc。fn foo_mut(&mut self) -> RepeatedMut<'_, i32>: 返回底层重复字段的可变句柄。参见RepeatedMutrustdoc。fn set_foo(&mut self, src: impl IntoProxied: 将底层重复字段设置为>) src中提供的新重复字段。接受RepeatedView、RepeatedMut或Repeated。参见Repeatedrustdoc。
对于不同的字段类型,只有 RepeatedView、RepeatedMut 和 Repeated 类型的相应泛型类型会改变。例如,给定一个 string 类型的字段,foo() 访问器将返回 RepeatedView<'_, ProtoString>。
映射字段
对于此 map 字段定义:
map<int32, int32> weight = 1;
编译器将生成以下 3 个访问器方法
fn weight(&self) -> protobuf::MapView<'_, i32, i32>: 返回底层映射的不可变视图。参见MapViewrustdoc。fn weight_mut(&mut self) -> protobuf::MapMut<'_, i32, i32>: 返回底层映射的可变句柄。参见MapMutrustdoc。fn set_weight(&mut self, src: protobuf::IntoProxied: 将底层映射设置为src。接受MapView、MapMut或Map。参见Maprustdoc。
对于不同的字段类型,只有 MapView、MapMut 和 Map 类型的相应泛型类型会改变。例如,给定一个 string 类型的字段,foo() 访问器将返回 MapView<'_, int32, ProtoString>。
Any
目前 Rust Protobuf 不会对 Any 进行特殊处理;它会像一个具有此定义的简单消息一样
message Any {
string type_url = 1;
bytes value = 2;
}
Oneof
给定像这样的 oneof 定义
oneof example_name {
int32 foo_int = 4;
string foo_string = 9;
...
}
编译器将为每个字段生成访问器(getter、setter、hazzer),就好像同一个字段被声明为 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 常量在 impl 中具有作用域,因此在 .proto 文件中添加的有益的额外前缀在 Rust 中将是多余的。
扩展(仅限 proto2)
Rust 扩展 API 目前仍在开发中。扩展字段将通过解析/序列化进行维护,并且在 C++ 互操作情况下,如果从 Rust 访问消息(并在消息复制或合并的情况下传播),则会保留所有设置的扩展。
竞技场分配(Arena Allocation)
尚未实现 arena 分配消息的 Rust API。
在内部,Protobuf Rust 在 upb 内核上使用竞技场(arenas),但在 C++ 内核上不使用。然而,对在 C++ 中分配到竞技场的消息的引用(const 和可变)可以安全地传递给 Rust 以进行访问或修改。
服务(Services)
尚未实现服务的 Rust API。