版本特性设置

Protobuf 版本特性及其如何影响 protobuf 行为。

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

Prototiller

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

特性

以下部分包含所有可以使用版本 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: 开放枚举将超出范围的值直接解析到其字段中。

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

版本 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_* 函数。

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

版本 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 标签在版本中不再存在,因为相应行为是使用 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";
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/proto3packed 选项在版本中迁移到的位置,用于 repeated 字段。

可用值

  • 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_view,但不能同时指定两者。

可用值

  • 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;
}

保留 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 文件,删除 packed 字段选项,并在您不希望使用 Proto2 行为 中设置的 EXPANDED 行为时,在字段级别添加 [features.repeated_field_encoding=PACKED]。对于转换为版本格式的 proto3 文件,在您不希望使用默认的 proto3 行为时,在字段级别添加 [features.repeated_field_encoding=EXPANDED]