版本的功能设置

Protobuf 版本功能以及它们如何影响 protobuf 行为。

本主题概述了 Edition 2023 中包含的功能。后续每个版本的功能都将添加到本主题中。我们在新闻部分中宣布新版本。

在新的模式定义内容中配置功能设置之前,请确保您了解为什么要使用它们。避免在功能上盲目照搬

Prototiller

Prototiller 是一个命令行工具,可将 proto2 和 proto3 定义文件转换为 Editions 语法。它尚未发布,但在本主题中被引用。

功能

以下部分包括 Edition 2023 中所有可使用功能配置的行为。保留 proto2 或 proto3 行为展示了如何覆盖默认行为,以便您的 proto 定义文件表现得像 proto2 或 proto3 文件。有关版本和功能如何协同工作以设置行为的更多信息,请参阅Protobuf 版本概述

以下每个部分都有一个条目,说明该功能适用于哪个范围。这可以包括文件、枚举、消息或字段。以下示例显示了应用于每个范围的模拟功能

edition = "2023";

// File-scope definition
option features.bar = BAZ;

enum Foo {
  // Enum-scope definition
  option features.bar = QUX;

  A = 1;
  B = 2;
}

message Corge {
  // Message-scope definition
  option features.bar = QUUX;

  // Field-scope definition
  Foo A = 1 [features.bar = GRAULT];
}

在此示例中,字段范围功能定义中的设置 GRAULT 覆盖了消息范围 QUUX 设置。

features.enum_type

此功能设置了如何处理未包含在已定义集合中的枚举值的行为。有关开放枚举和封闭枚举的更多信息,请参阅枚举行为

此功能不影响 proto3 文件,因此本节没有 proto3 文件的前后对比。

可用值

  • CLOSED: 封闭枚举将超出范围的枚举值存储在未知字段集中。
  • OPEN: 开放枚举直接将超出范围的值解析到其字段中。

适用范围: 文件、枚举

Edition 2023 中的默认行为: OPEN

proto2 中的行为: CLOSED

proto3 中的行为: OPEN

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

enum Foo {
  A = 2;
  B = 4;
  C = 6;
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

enum Foo {
  option features.enum_type = CLOSED;
  A = 2;
  B = 4;
  C = 6;
}

features.field_presence

此功能设置了跟踪字段存在性的行为,即 protobuf 字段是否具有值的概念。

可用值

  • LEGACY_REQUIRED:该字段是解析和序列化所必需的。任何显式设置的值都将被序列化到网络上(即使它与默认值相同)。
  • EXPLICIT:该字段具有显式的存在性跟踪。任何显式设置的值都将被序列化到网络上(即使它与默认值相同)。对于单数原始字段,将为设置为 EXPLICIT 的字段生成 has_* 函数。
  • IMPLICIT:该字段没有存在性跟踪。默认值不会被序列化到网络上(即使它是显式设置的)。不会为设置为 IMPLICIT 的字段生成 has_* 函数。

适用范围: 文件、字段

Edition 2023 中的默认值: EXPLICIT

proto2 中的行为: EXPLICIT

proto3 中的行为: IMPLICIT,除非字段具有 optional 标签,在这种情况下,它的行为类似于 EXPLICIT。有关更多信息,请参阅Proto3 API 中的存在性

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

message Foo {
  required int32 x = 1;
  optional int32 y = 2;
  repeated int32 z = 3;
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

message Foo {
  int32 x = 1 [features.field_presence = LEGACY_REQUIRED];
  int32 y = 2;
  repeated int32 z = 3;
}

以下显示了一个 proto3 文件

syntax = "proto3";

message Bar {
  int32 x = 1;
  optional int32 y = 2;
  repeated int32 z = 3;
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";
option features.field_presence = IMPLICIT;

message Bar {
  int32 x = 1;
  int32 y = 2 [features.field_presence = EXPLICIT];
  repeated int32 z = 3;
}

请注意,requiredoptional 标签在 Editions 中不再存在,因为相应的行为是通过 field_presence 功能显式设置的。

features.json_format

此功能设置了 JSON 解析和序列化的行为。

此功能不影响 proto3 文件,因此本节没有 proto3 文件的前后对比。Editions 行为与 proto3 中的行为匹配。

可用值

  • ALLOW:运行时必须允许 JSON 解析和序列化。在 proto 级别应用检查,以确保存在到 JSON 的明确定义的映射。
  • LEGACY_BEST_EFFORT:运行时尽力解析和序列化 JSON。允许某些 proto 可能会导致运行时出现未指定的行为(例如 many:1 或 1:many 映射)。

适用范围: 文件、消息、枚举

Edition 2023 中的默认行为: ALLOW

proto2 中的行为: LEGACY_BEST_EFFORT

proto3 中的行为: ALLOW

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

message Foo {
  // Warning only
  string bar = 1;
  string bar_ = 2;
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";
features.json_format = LEGACY_BEST_EFFORT;

message Foo {
  string bar = 1;
  string bar_ = 2;
}

features.message_encoding

此功能设置了序列化时编码字段的行为。

此功能不影响 proto3 文件,因此本节没有 proto3 文件的前后对比。

根据语言的不同,“类似组”的字段在生成的代码和文本格式中可能具有一些意外的大写,以便提供与 proto2 的向后兼容性。如果满足以下所有条件,则消息字段是“类似组”的

  • 已指定 DELIMITED 消息编码
  • 消息类型与字段在同一范围内定义
  • 字段名称与小写类型名称完全相同

可用值

  • LENGTH_PREFIXED:字段使用消息结构中描述的 LEN 线类型进行编码。
  • DELIMITED:消息类型字段编码为

适用范围: 文件、字段

Edition 2023 中的默认行为: LENGTH_PREFIXED

proto2 中的行为: LENGTH_PREFIXED,但组除外,组默认为 DELIMITED

proto3 中的行为: LENGTH_PREFIXED。Proto3 不支持 DELIMITED

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

message Foo {
  group Bar = 1 {
    optional int32 x = 1;
    repeated int32 y = 2;
  }
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

message Foo {
  message Bar {
    int32 x = 1;
    repeated int32 y = 2;
  }
  Bar bar = 1 [features.message_encoding = DELIMITED];
}

features.repeated_field_encoding

此功能是 proto2/proto3 packed 选项 for repeated 字段已在 Editions 中迁移到的功能。

可用值

  • PACKED:原始类型的 Repeated 字段编码为包含每个元素连接的单个 LEN 记录。
  • EXPANDEDRepeated 字段各自使用每个值的字段编号进行编码。

适用范围: 文件、字段

Edition 2023 中的默认行为: PACKED

proto2 中的行为: EXPANDED

proto3 中的行为: PACKED

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

message Foo {
  repeated int32 bar = 6 [packed=true];
  repeated int32 baz = 7;
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";
option features.repeated_field_encoding = EXPANDED;

message Foo {
  repeated int32 bar = 6 [features.repeated_field_encoding=PACKED];
  repeated int32 baz = 7;
}

以下显示了一个 proto3 文件

syntax = "proto3";

message Foo {
  repeated int32 bar = 6;
  repeated int32 baz = 7 [packed=false];
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

message Foo {
  repeated int32 bar = 6;
  repeated int32 baz = 7 [features.repeated_field_encoding=EXPANDED];
}

features.utf8_validation

此功能设置了字符串的验证方式。它适用于所有语言,除非有特定于语言的 utf8_validation 功能覆盖它。有关 Java 语言特定功能,请参阅features.(pb.java).utf8_validation

此功能不影响 proto3 文件,因此本节没有 proto3 文件的前后对比。

可用值

  • VERIFY:运行时应验证 UTF-8。这是默认的 proto3 行为。
  • NONE:该字段的行为类似于网络上未验证的 bytes 字段。解析器可能会以不可预测的方式处理这种类型的字段,例如替换无效字符。这是默认的 proto2 行为。

适用范围: 文件、字段

Edition 2023 中的默认行为: VERIFY

proto2 中的行为: NONE

proto3 中的行为: VERIFY

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

message MyMessage {
  string foo = 1;
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

message MyMessage {
  string foo = 1 [features.utf8_validation = NONE];
}

特定于语言的功能

某些功能适用于特定语言,而不适用于其他语言中的相同 proto。使用这些功能需要您从语言的运行时导入相应的 *_features.proto 文件。以下部分中的示例显示了这些导入。

features.(pb.cpp/pb.java).legacy_closed_enum

语言: C++,Java

此功能确定具有开放枚举类型的字段是否应表现得像封闭枚举。这允许版本重现 proto2 和 proto3 中 Java 和 C++ 的不符合规范的行为

此功能不影响 proto3 文件,因此本节没有 proto3 文件的前后对比。

可用值

  • true:将枚举视为封闭枚举,而与 enum_type 无关。
  • false:遵守在 enum_type 中设置的任何内容。

适用范围: 文件、字段

Edition 2023 中的默认行为: false

proto2 中的行为: true

proto3 中的行为: false

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

import "myproject/proto3file.proto";

message Msg {
  myproject.proto3file.Proto3Enum name = 1;
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

import "myproject/proto3file.proto";

import "google/protobuf/cpp_features.proto";
import "google/protobuf/java_features.proto";

message Msg {
  myproject.proto3file.Proto3Enum name = 1 [
    features.(pb.cpp).legacy_closed_enum = true,
    features.(pb.java).legacy_closed_enum = true
  ];
}

features.(pb.cpp).string_type

语言: C++

此功能确定生成的代码应如何处理字符串字段。这取代了 proto2 和 proto3 中的 ctype 选项,并提供了一个新的 string_view 功能。在 Edition 2023 中,可以在字段上指定 ctypestring_type,但不能同时指定两者。

可用值

  • VIEW:为字段生成 string_view 访问器。这将是未来版本中的默认设置。
  • CORD:为字段生成 Cord 访问器。
  • STRING:为字段生成 string 访问器。

适用范围: 文件、字段

Edition 2023 中的默认行为: STRING

proto2 中的行为: STRING

proto3 中的行为: STRING

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

message Foo {
  optional string bar = 6;
  optional string baz = 7 [ctype = CORD];
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

import "google/protobuf/cpp_features.proto";

message Foo {
  string bar = 6;
  string baz = 7 [features.(pb.cpp).string_type = CORD];
}

以下显示了一个 proto3 文件

syntax = "proto3"

message Foo {
  string bar = 6;
  string baz = 7 [ctype = CORD];
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

import "google/protobuf/cpp_features.proto";

message Foo {
  string bar = 6;
  string baz = 7 [features.(pb.cpp).string_type = CORD];
}

features.(pb.java).utf8_validation

语言: Java

此特定于语言的功能使您能够在字段级别覆盖 Java 的文件级别设置。

此功能不影响 proto3 文件,因此本节没有 proto3 文件的前后对比。

可用值

  • DEFAULT:行为与 features.utf8_validation 设置的行为相匹配。
  • VERIFY:覆盖文件级别 features.utf8_validation 设置,强制仅对 Java 设置为 VERIFY

适用范围: 字段、文件

Edition 2023 中的默认行为: DEFAULT

proto2 中的行为: DEFAULT

proto3 中的行为: DEFAULT

以下代码示例显示了一个 proto2 文件

syntax = "proto2";

option java_string_check_utf8=true;

message MyMessage {
  string foo = 1;
  string bar = 2;
}

运行 Prototiller 后,等效代码可能如下所示

edition = "2023";

import "google/protobuf/java_features.proto";

option features.utf8_validation = NONE;
option features.(pb.java).utf8_validation = VERIFY;
message MyMessage {
  string foo = 1;
}

features.(pb.java).large_enum

语言: Java

此特定于语言的功能使您能够采用处理 Java 中大型枚举的新功能,而不会导致编译器错误。

这是新行为,因此不影响 proto2 或 proto3 模式定义文件。

可用值

  • true:Java 枚举将使用新功能。
  • false:Java 枚举将继续使用 Java 枚举。

适用范围: 枚举

Edition 2023 中的默认行为: false

proto2 中的行为: false

proto3 中的行为: false

保留 proto2 或 proto3 行为

您可能希望迁移到版本格式,但暂不处理生成代码行为方式的更新。本节介绍 Prototiller 工具对您的 .proto 文件所做的更改,以使 Edition 2023 proto 的行为类似于 proto2 或 proto3 文件。

在文件级别进行这些更改后,您将获得 proto2 或 proto3 默认值。您可以在较低级别(消息级别、字段级别)覆盖,以考虑其他行为差异(例如required、proto3 optional)或者如果您希望您的定义仅大致类似于 proto2 或 proto3。

我们建议使用 Prototiller,除非您有不使用的特定原因。要手动应用所有这些而不是使用 Prototiller,请将以下部分的内容添加到您的 .proto 文件的顶部。

Proto2 行为

edition = "2023";

import "google/protobuf/cpp_features.proto";
import "google/protobuf/java_features.proto";

option features.field_presence = EXPLICIT;
option features.enum_type = CLOSED;
option features.repeated_field_encoding = EXPANDED;
option features.json_format = LEGACY_BEST_EFFORT;
option features.utf8_validation = NONE;
option features.(pb.cpp).legacy_closed_enum = true;
option features.(pb.java).legacy_closed_enum = true;

Proto3 行为

// proto3 behaviors
edition = "2023";

import "google/protobuf/cpp_features.proto";
import "google/protobuf/java_features.proto";

option features.field_presence = IMPLICIT;
option features.enum_type = OPEN;
// `packed=false` needs to be transformed to field-level repeated_field_encoding
// features in Editions syntax
option features.json_format = ALLOW;
option features.utf8_validation = VERIFY;
option features.(pb.cpp).legacy_closed_enum = false;
option features.(pb.java).legacy_closed_enum = false;

注意事项和例外

本节介绍如果您选择不使用 Prototiller,则需要手动进行的更改。

在大多数情况下,设置上一节中显示的文件级别默认值会设置默认行为,但也有一些例外。

  • optional:删除所有 optional 标签的实例,如果文件默认值为 IMPLICIT,则将 features.field_presence 更改为 EXPLICIT
  • required:删除所有 required 标签的实例,并在字段级别添加 features.field_presence=LEGACY_REQUIRED 选项。
  • groups:将 groups 解包到单独的消息中,并在字段级别添加 features.message_encoding = DELIMITED 选项。有关更多信息,请参阅 features.message_encoding
  • java_string_check_utf8:删除此文件选项,并将其替换为 features.(pb.java).utf8_validation。您需要导入 Java 功能,如特定于语言的功能中所述。
  • packed:对于转换为版本格式的 proto2 文件,如果您不希望在Proto2 行为中设置的 EXPANDED 行为,请删除 packed 字段选项并在字段级别添加 [features.repeated_field_encoding=PACKED]。对于转换为版本格式的 proto3 文件,如果您不希望默认的 proto3 行为,请在字段级别添加 [features.repeated_field_encoding=EXPANDED]