Rust 生成代码指南
此页面准确描述了协议缓冲区编译器为任何给定协议定义生成的 Rust 代码。
proto2 和 proto3 生成代码之间的任何差异都会被重点说明。在阅读本文档之前,您应该先阅读proto2 语言指南和/或proto3 语言指南。
Protobuf Rust
Protobuf Rust 是协议缓冲区的一种实现,设计用于可构建在其他现有协议缓冲区实现之上,我们称之为“内核”。
支持多个非 Rust 内核的决定对我们的公共 API 产生了显著影响,包括选择使用像 ProtoStr
这样的自定义类型而不是像 str
这样的 Rust 标准库类型。有关此主题的更多信息,请参阅Rust Proto 设计决策。
生成的文件名
每个 rust_proto_library
将被编译为一个 crate。最重要的是,对于相应 proto_library
的 srcs
中的每个 .proto
文件,会生成一个 Rust 文件,所有这些文件构成一个单独的 crate。
编译器生成的文件因内核而异。一般来说,输出文件的名称是通过取 .proto
文件的名称并替换扩展名来计算的。
生成文件
- C++ 内核
.c.pb.rs
- 生成的 Rust 代码.pb.thunks.cc
- 生成的 C++ thunks(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,以及所有“辅助文件”对应文件中定义的所有符号的 re-exports。
包
与大多数其他语言不同,.proto
文件中的 package
声明不用于 Rust codegen。相反,每个 rust_proto_library(name = "some_rust_proto")
目标会发出一个名为 some_rust_proto
的 crate,其中包含目标中所有 .proto
文件的生成代码。
消息
给定消息声明
message Foo {}
编译器会生成一个名为 Foo
的结构体。Foo
结构体定义了以下方法:
fn new() -> Self
: 创建Foo
的新实例。fn parse(data: &[u8]) -> Result<Self, protobuf::ParseError>
: 如果data
包含有效的Foo
线格式表示,则将data
解析为Foo
实例。否则,函数返回一个错误。fn clear_and_parse(&mut self, data: &[u8]) -> Result<(), ParseError>
: 类似于按顺序调用.clear()
和parse()
。fn serialize(&self) -> Result<Vec<u8>, SerializeError>
: 将消息序列化为 Protobuf 线格式。序列化可能会失败,但很少会。失败原因包括超出最大消息大小、内存不足以及必需字段(proto2)未设置。fn merge_from(&mut self, other)
: 将self
与other
合并。fn as_view(&self) -> FooView<'_>
: 返回Foo
的不可变句柄(视图)。这在代理类型部分有进一步介绍。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
),而是需要使用类型来表达这些概念——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 和 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()
将返回true
,foo()
将返回value
。fn clear_foo(&mut self)
: 清除字段的值。调用此方法后,has_foo()
将返回false
,foo()
将返回默认值。
对于其他数字字段类型(包括 bool
),int32
将根据标量值类型表替换为相应的 Rust 类型。
隐式存在数字字段 (proto3)
对于这些字段定义:
int32 foo = 1;
fn foo(&self) -> i32
: 返回字段的当前值。如果字段未设置,则返回0
。fn set_foo(&mut self, val: i32)
: 设置字段的值。调用此方法后,foo()
将返回值。
对于其他数字字段类型(包括 bool
),int32
将根据标量值类型表替换为相应的 Rust 类型。
可选字符串/字节字段 (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()
将返回false
,foo()
将返回默认值。
对于 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()
将返回value
,has_foo()
将返回true
。fn has_foo(&self) -> bool
: 如果字段已设置,则返回true
。fn clear_foo(&mut self)
: 清除字段的值。调用此方法后,has_foo()
将返回false
,foo()
将返回默认值。
对于 bytes
类型的字段,编译器将生成 ProtoBytes
类型。
支持 Cord 的单数字符串和字节字段
[ctype = CORD]
使得字节和字符串可以在 C++ Protobuf 中作为 absl::Cord 存储。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<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()
返回value
,has_foo()
返回true
。fn has_foo(&self) -> bool
: 如果字段已设置,则返回true
。fn clear_foo(&mut self)
: 清除字段的值。调用此方法后,has_foo()
返回false
,foo()
返回默认值。Cords 尚未实现。
对于 bytes
类型的字段,编译器会生成 ProtoBytesCow
类型。
可选枚举字段 (proto2 和 proto3)
给定枚举类型
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);
}
对于以下任一字段定义:
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()
将返回true
,foo()
将返回value
。fn clear_foo(&mut self)
: 清除字段的值。调用此方法后,has_foo()
将返回 false,foo()
将返回默认值。
隐式存在枚举字段 (proto3)
给定枚举类型
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 foo(&self) -> Bar
: 返回字段当前值。如果字段未设置,则返回默认值。fn set_foo(&mut self, value: Bar)
: 设置字段的值。调用此方法后,has_foo()
将返回true
,foo()
将返回value
。
可选嵌入消息字段 (proto2 和 proto3)
给定消息类型
message Bar {}
对于任何这些字段定义:
//proto2
optional Bar foo = 1;
//proto3
Bar foo = 1;
optional Bar foo = 1;
编译器将生成以下访问器方法:
fn foo(&self) -> BarView<'_>
: 返回字段当前值的视图。如果字段未设置,则返回空消息。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>
: 返回底层重复字段的视图。fn foo_mut(&mut self) -> RepeatedMut<'_, i32>
: 返回底层重复字段的可变句柄。fn set_foo(&mut self, src: impl IntoProxied<Repeated<i32>>)
: 将底层重复字段设置为src
中提供的新重复字段。
对于不同的字段类型,只有 RepeatedView
, RepeatedMut
和 Repeated
类型的相应泛型类型会改变。例如,给定一个 string
类型的字段,foo()
访问器将返回一个 RepeatedView<'_, ProtoString>
。
Map 字段
对于此 map 字段定义:
map<int32, int32> weight = 1;
编译器将生成以下 3 个访问器方法:
fn weight(&self) -> protobuf::MapView<'_, i32, i32>
: 返回底层 map 的不可变视图。fn weight_mut(&mut self) -> protobuf::MapMut<'_, i32, i32>
: 返回底层 map 的可变句柄。fn set_weight(&mut self, src: protobuf::IntoProxied<Map<i32, i32>>)
: 将底层 map 设置为src
。
对于不同的字段类型,只有 MapView
、MapMut
和 Map
类型的相应泛型类型会发生变化。例如,给定一个 string
类型的字段,foo()
访问器将返回一个 MapView<'_, int32, ProtoString>
。
Any
Protobuf Rust 目前并未特殊处理 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、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 分配
arena 分配消息的 Rust API 尚未实现。
在内部,Protobuf Rust 在 upb 内核上使用 arena,但在 C++ 内核上不使用。然而,可以在 C++ 中进行 arena 分配的消息的引用(const 和 mutable)可以安全地传递给 Rust 进行访问或修改。
服务
服务的 Rust API 尚未实现。