版本的特性设置

Protobuf 版本的特性以及它们如何影响 Protobuf 行为。

本主题概述了 2023 版包含的特性。后续版本的特性将添加到本主题中。我们在新闻部分宣布新版本。

在新的模式定义内容中配置特性设置之前,请确保您理解为何使用它们。避免对特性进行货拜式编程

Prototiller

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

特性

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

特性设置适用于不同的级别

文件级别:这些设置适用于所有没有覆盖设置的元素(消息、字段、枚举等)。

非嵌套级别:消息、枚举和服务可以覆盖文件级别设置。它们适用于其内部所有未被覆盖的内容(消息字段、枚举值),但不适用于其他同级消息和枚举。

嵌套级别:Oneofs、消息和枚举可以覆盖它们所嵌套的消息中的设置。

最低级别:字段、扩展、枚举值、扩展范围和方法是您可以覆盖设置的最低级别。

以下每个部分都有一个注释,说明该特性可以应用于哪个范围。以下示例展示了一个应用于各个范围的模拟特性

edition = "2023";

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

enum Foo {
  // Enum (non-nested scope) definition
  option features.bar = QUX;

  A = 1;
  B = 2;
}

message Corge {
  // Message (non-nested scope) definition
  option features.bar = QUUX;

  message Garply {
    // Message (nested scope) definition
    option features.bar = WALDO;
    string id = 1;
  }

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

在此示例中,最低级别范围特性定义中的设置“GRAULT" 覆盖了非嵌套范围的设置“QUUX”。在 Garply 消息中,“WALDO” 覆盖了“QUUX”。

features.enum_type

此特性设置了如何处理不在定义集内的枚举值的行为。有关开放枚举和封闭枚举的更多信息,请参见枚举行为

此特性不影响 proto3 文件,因此本节没有 proto3 文件的修改前后对比。

可用值

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

适用于以下范围:文件、枚举

2023 版中的默认行为: OPEN

proto2 中的行为: CLOSED

proto3 中的行为: OPEN

注意:不同模式元素上的特性设置具有不同的范围

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

syntax = "proto2";

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

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

edition = "2023";

enum Foo {
  // Setting the enum_type feature overrides the default OPEN enum
  option features.enum_type = CLOSED;
  A = 2;
  B = 4;
  C = 6;
}

features.field_presence

此特性设置了字段存在性(即 Protobuf 字段是否有值)的跟踪行为。

可用值

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

适用于以下范围:文件、字段

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 {
  // Setting the field_presence feature retains the proto2 required behavior
  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";
// Setting the file-level field_presence feature matches the proto3 implicit default
option features.field_presence = IMPLICIT;

message Bar {
  int32 x = 1;
  // Setting the field_presence here retains the explicit state that the proto3
  // field has because of the optional syntax
  int32 y = 2 [features.field_presence = EXPLICIT];
  repeated int32 z = 3;
}

请注意,requiredoptional 标签在版本中不再存在,因为相应的行为通过 field_presence 特性显式设置。

features.json_format

此特性设置了 JSON 解析和序列化的行为。

此特性不影响 proto3 文件,因此本节没有 proto3 文件的修改前后对比。版本行为与 proto3 中的行为一致。

可用值

  • ALLOW: 运行时必须允许 JSON 解析和序列化。在 proto 级别进行检查,以确保与 JSON 有明确的映射。
  • LEGACY_BEST_EFFORT: 运行时尽力解析和序列化 JSON。允许某些可能导致运行时行为未指定的 proto(例如多对一或一对多映射)。

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

2023 版中的默认行为: ALLOW

proto2 中的行为: LEGACY_BEST_EFFORT

proto3 中的行为: ALLOW

注意:不同模式元素上的特性设置具有不同的范围

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

syntax = "proto2";

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

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

edition = "2023";
option 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: 消息类型的字段编码为

适用于以下范围:文件、字段

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 中 repeated 字段的packed 选项在版本中迁移到的内容。

可用值

  • PACKED: 原始类型的 repeated 字段编码为单个 LEN 记录,其中包含连接在一起的每个元素。
  • EXPANDED: repeated 字段的每个值都使用字段号编码。

适用于以下范围:文件、字段

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 的默认行为。

适用于以下范围:文件、字段

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

此特性决定了具有开放枚举类型的字段是否应表现得像封闭枚举。这允许版本在 Java 和 C++ 中重现 proto2 和 proto3 的非标准行为

此特性不影响 proto3 文件,因此本节没有 proto3 文件的修改前后对比。

可用值

  • true: 无论 enum_type 如何,都将枚举视为封闭。
  • false: 尊重 enum_type 中的设置。

适用于以下范围:文件、字段

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 特性。在 2023 版中,可以在字段上指定 ctypestring_type,但不能同时指定两者。

可用值

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

适用于以下范围:文件、字段

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

适用于以下范围:字段、文件

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;
  string bar = 2;
}

features.(pb.java).large_enum

语言: Java

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

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

可用值

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

适用于以下范围:枚举

2023 版中的默认行为: false

proto2 中的行为: false

proto3 中的行为: false

注意:不同模式元素上的特性设置具有不同的范围

保留 proto2 或 proto3 行为

您可能希望迁移到版本格式,但暂时不想处理生成代码行为方式的更新。本节展示了 Prototiller 工具对您的 .proto 文件所做的更改,以使 2023 版 proto 表现得像 proto2 或 proto3 文件。

在文件级别进行这些更改后,您将获得 proto2 或 proto3 的默认设置。您可以在较低级别(消息级别、字段级别)进行覆盖,以考虑额外的行为差异(例如必需字段、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 文件,移除 packed 字段选项,当您不希望获得在Proto2 行为中设置的 EXPANDED 行为时,请在字段级别添加 [features.repeated_field_encoding=PACKED]。对于转换为版本格式的 proto3 文件,当您不希望获得默认 proto3 行为时,请在字段级别添加 [features.repeated_field_encoding=EXPANDED]