Protobuf 版本概览Protobuf 版本功能的概览。Protobuf 版本取代了我们过去一直使用的 proto2 和 proto3 语法。你不再需要在 proto 定义文件的顶部添加 syntax = "proto2" 或 syntax = "proto3",而是使用版本号(如 edition = "2024")来指定文件的默认行为。版本机制使该语言能够随着时间的推移而逐步演进。
与旧版本中硬编码的行为不同,版本代表了一系列特性的集合,每个特性都有一个默认值(行为)。特性是文件、消息、字段、枚举等上面的选项,用于指定 protoc、代码生成器和 Protobuf 运行时的行为。当你选择的版本的默认行为不符合你的需求时,你可以在这些不同级别(文件、消息、字段……)上显式地覆盖某个行为。你也可以覆盖你的覆盖设置。本主题后面关于词法作用域的章节对此有更详细的介绍。
最新发布的版本是 2023。
本主题中的示例展示了 2024 版本的特性,但 2024 版本目前处于预发布审查阶段,尚不推荐用于生产代码。
特性的生命周期版本为一个特性的生命周期提供了基本的增量单位。特性有其预期的生命周期:引入、更改默认行为、弃用,然后移除。例如:
2031 版本创建了 feature.amazing_new_feature,其默认值为 false。此值保持了与所有早期版本相同的行为。也就是说,它默认没有影响。并非所有新特性都会默认为无操作选项,但在此示例中,amazing_new_feature 是这样。
开发者将他们的 .proto 文件更新为 edition = "2031"。
之后的某个版本,如 2033 版本,将 feature.amazing_new_feature 的默认值从 false 切换为 true。这是所有 proto 所期望的行为,也是 Protobuf 团队创建该特性的原因。
使用 Prototiller 工具将早期版本的 proto 文件迁移到 2033 版本时,会根据需要添加显式的 feature.amazing_new_feature = false 条目,以继续保留以前的行为。当开发者希望新行为应用于其 .proto 文件时,可以移除这些新添加的设置。
在某个时间点,feature.amazing_new_feature 会在某个版本中被标记为弃用,并在之后的版本中被移除。
当一个特性被移除时,用于该行为的代码生成器和支持它的运行时库也可能被移除。不过,时间线会很宽裕。沿用生命周期前面步骤的例子,弃用可能发生在 2034 版本,但直到大约两年后的 2036 版本才会被移除。移除一个特性总是会引发主版本号的提升。
你将有 Google 迁移的整个窗口期加上弃用窗口期来升级你的代码。
前面的生命周期示例中,特性的值为布尔值,但特性也可以使用枚举。例如,features.field_presence 的值有 LEGACY_REQUIRED、EXPLICIT 和 IMPLICIT。
迁移到 Protobuf 版本版本不会破坏现有的二进制文件,也不会改变消息的二进制、文本或 JSON 序列化格式。2023 版本的发布尽可能地减少了破坏性。它建立了基线,并将 proto2 和 proto3 的定义合并成一个新的单一的定义格式。
随着更多版本的发布,特性的默认行为可能会改变。你可以让 Prototiller 对你的 .proto 文件进行一次无操作的转换,或者你可以选择接受部分或全部的新行为。版本计划大约每年发布一次。
从 Proto2 迁移到版本本节展示了一个 proto2 文件,以及在运行 Prototiller 工具将其定义文件更改为使用 Protobuf 版本语法后可能的样子。
Proto2 语法// proto2 file
syntax = "proto2";
package com.example;
message Player {
// in proto2, optional fields have explicit presence
optional string name = 1 [default = "N/A"];
// proto2 still supports the problematic "required" field rule
required int32 id = 2;
// in proto2 this is not packed by default
repeated int32 scores = 3;
enum Handed {
HANDED_UNSPECIFIED = 0;
HANDED_LEFT = 1;
HANDED_RIGHT = 2;
HANDED_AMBIDEXTROUS = 3;
}
// in proto2 enums are closed
optional Handed handed = 4;
reserved "gender";
}
版本语法// Edition version of proto2 file
edition = "2024";
package com.example;
option features.utf8_validation = NONE;
option features.enforce_naming_style = STYLE_LEGACY;
option features.default_symbol_visibility = EXPORT_ALL;
// Sets the default behavior for C++ strings
option features.(pb.cpp).string_type = STRING;
message Player {
// fields have explicit presence, so no explicit setting needed
string name = 1 [default = "N/A"];
// to match the proto2 behavior, LEGACY_REQUIRED is set at the field level
int32 id = 2 [features.field_presence = LEGACY_REQUIRED];
// to match the proto2 behavior, EXPANDED is set at the field level
repeated int32 scores = 3 [features.repeated_field_encoding = EXPANDED];
export enum Handed {
// this overrides the default editions behavior, which is OPEN
option features.enum_type = CLOSED;
HANDED_UNSPECIFIED = 0;
HANDED_LEFT = 1;
HANDED_RIGHT = 2;
HANDED_AMBIDEXTROUS = 3;
}
Handed handed = 4;
reserved gender;
}
从 Proto3 迁移到版本本节展示了一个 proto3 文件,以及在运行 Prototiller 工具将其定义文件更改为使用 Protobuf 版本语法后可能的样子。
Proto3 语法// proto3 file
syntax = "proto3";
package com.example;
message Player {
// in proto3, optional fields have explicit presence
optional string name = 1;
// in proto3 no specified field rule defaults to implicit presence
int32 id = 2;
// in proto3 this is packed by default
repeated int32 scores = 3;
enum Handed {
HANDED_UNSPECIFIED = 0;
HANDED_LEFT = 1;
HANDED_RIGHT = 2;
HANDED_AMBIDEXTROUS = 3;
}
// in proto3 enums are open
optional Handed handed = 4;
reserved "gender";
}
版本语法// Editions version of proto3 file
edition = "2024";
package com.example;
option features.utf8_validation = NONE;
option features.enforce_naming_style = STYLE_LEGACY;
option features.default_symbol_visibility = EXPORT_ALL;
// Sets the default behavior for C++ strings
option features.(pb.cpp).string_type = STRING;
message Player {
// fields have explicit presence, so no explicit setting needed
string name = 1 [default = "N/A"];
// to match the proto3 behavior, IMPLICIT is set at the field level
int32 id = 2 [features.field_presence = IMPLICIT];
// PACKED is the default state, and is provided just for illustration
repeated int32 scores = 3 [features.repeated_field_encoding = PACKED];
export enum Handed {
HANDED_UNSPECIFIED = 0;
HANDED_LEFT = 1;
HANDED_RIGHT = 2;
HANDED_AMBIDEXTROUS = 3;
}
Handed handed = 4;
reserved gender;
}
词法作用域版本语法支持词法作用域,并为每个特性提供了一个允许的目标列表。例如,在 2023 版本中,特性只能在文件级别或最低粒度级别上指定。词法作用域的实现使你能够为一个特性在整个文件范围内设置默认行为,然后在消息、字段、枚举、枚举值、oneof、服务或方法级别上覆盖该行为。在较高层级(文件、消息)进行的设置,在同一作用域内(字段、枚举值)没有进行设置时会生效。任何未显式设置的特性都遵循 .proto 文件所用版本中定义的行为。
以下代码示例展示了在文件、字段和枚举级别上设置的一些特性。
edition = "2024";
option features.enum_type = CLOSED;
message Person {
string name = 1;
int32 id = 2 [features.field_presence = IMPLICIT];
enum Pay_Type {
PAY_TYPE_UNSPECIFIED = 1;
PAY_TYPE_SALARY = 2;
PAY_TYPE_HOURLY = 3;
}
enum Employment {
option features.enum_type = OPEN;
EMPLOYMENT_UNSPECIFIED = 0;
EMPLOYMENT_FULLTIME = 1;
EMPLOYMENT_PARTTIME = 2;
}
Employment employment = 4;
}
在前面的示例中,存在性(presence)特性被设置为 IMPLICIT;如果未设置,它将默认为 EXPLICIT。Pay_Type enum 将是 CLOSED,因为它应用了文件级别的设置。然而,Employment enum 将是 OPEN,因为它是在枚举内部设置的。
Prototiller当 Prototiller 工具发布时,我们将提供迁移指南和迁移工具,以简化到版本之间以及版本内部的迁移。该工具将使你能够:
大规模地将 proto2 和 proto3 定义文件转换为新的版本语法将文件从一个版本迁移到另一个版本以其他方式操作 proto 文件向后兼容性我们正在构建 Protobuf 版本,使其尽可能地减少破坏性。例如,你可以将 proto2 和 proto3 定义导入到基于版本的定义文件中,反之亦然。
// file myproject/foo.proto
syntax = "proto2";
enum Employment {
EMPLOYMENT_UNSPECIFIED = 0;
EMPLOYMENT_FULLTIME = 1;
EMPLOYMENT_PARTTIME = 2;
}
// file myproject/edition.proto
edition = "2024";
import "myproject/foo.proto";
当你从 proto2 或 proto3 迁移到版本时,生成的代码会发生变化,但线路格式(wire format)不会。你仍然可以使用版本语法的 proto 定义来访问 proto2 和 proto3 的数据文件或文件流。
语法变更与 proto2 和 proto3 相比,版本语法有一些语法变更。
语法描述你使用 edition 元素代替 syntax 元素。
syntax = "proto2";
syntax = "proto3";
edition = "2028";
保留名称在保留字段名和枚举值名时,你不再需要将它们放在引号中。
reserved foo, bar;
Group 语法在 proto2 中可用的 Group 语法在版本中已被移除。Group 使用的特殊线路格式仍然可以通过使用 DELIMITED 消息编码来获得。
Required 标签仅在 proto2 中可用的 required 标签在版本中不可用。其底层功能仍然可以通过使用 features.field_presence=LEGACY_REQUIRED 来获得。
import option2024 版本增加了对选项导入的支持,使用 import option 语法。
选项导入必须位于任何其他 import 语句之后。
与普通的 import 语句不同,选项导入仅导入在 .proto 文件中定义的自定义选项,而不导入其他符号。
这意味着消息和枚举被排除在选项导入之外。在下面的示例中,Bar 消息不能在 foo.proto 中用作字段类型,但类型为 Bar 的选项仍然可以设置。
// bar.proto
edition = "2024";
import "google/protobuf/descriptor.proto";
message Bar {
bool bar = 1;
}
extend proto2.FileOptions {
bool file_opt1 = 5000;
Bar file_opt2 = 5001;
}
// foo.proto:
edition = "2024";
import option "bar.proto";
option (file_opt1) = true;
option (file_opt2) = {bar: true};
message Foo {
// Bar bar = 1; // This is not allowed
}
选项导入不需要为其符号生成代码,因此应在 proto_library 中作为 option_deps 提供,而不是 deps。这样可以避免生成无法访问的代码。
proto_library(
name = "foo",
srcs = ["foo.proto"],
option_deps = [":custom_option_proto"]
)
在导入 protobuf 语言特性和其他自定义选项时,强烈建议使用选项导入和 option_deps,以避免生成不必要的代码。
这取代了在 2024 版本中被移除的 import weak。
export / local 关键字在 2024 版本中增加了 export 和 local 关键字,作为可导入符号可见性的修饰符,用于修改由 features.default_symbol_visibility 指定的默认行为。
这控制了哪些符号可以从其他 proto 文件导入,但不会影响代码生成。
在 2024 版本中,默认情况下,这些关键字可以设置在所有 message 和 enum 符号上。然而,default_symbol_visibility 特性的某些值会进一步限制哪些符号是可导出的。
示例
// Top-level symbols are exported by default in Edition 2024
message LocalMessage {
int32 baz = 1;
// Nested symbols are local by default in Edition 2024; applying the `export`
// keyword overrides this
export enum ExportedNestedEnum {
UNKNOWN_EXPORTED_NESTED_ENUM_VALUE = 0;
}
}
// The `local` keyword overrides the default behavior of exporting messages
local message AnotherMessage {
int32 foo = 1;
}
import weak 和弱字段选项自 2024 版本起,不再允许弱导入。
如果你之前依赖 import weak 来声明“弱依赖”——即为 C++ 和 Go 导入自定义选项而不生成代码——你现在应该迁移到使用 import option。
有关更多详细信息,请参阅 import option。
ctype 字段选项自 2024 版本起,不再允许使用 ctype 字段选项。请改用 string_type 特性。
有关更多详细信息,请参阅 features.(pb.cpp).string_type。
java_multiple_files 文件选项自 2024 版本起,java_multiple_files 文件选项不再可用。请改用 features.(pb.java).nest_in_file_class Java 特性。