Protobuf 版本概览

Protobuf 版本功能的概述。

Protobuf 版本取代了我们之前在 Protocol Buffers 中使用的 proto2 和 proto3 指定。您不再需要在 proto 定义文件的顶部添加 syntax = "proto2"syntax = "proto3",而是使用版本号,例如 edition = "2023",来指定文件将具有的默认行为。版本允许语言随着时间的推移逐步发展。

版本表示一组具有每个特性默认值(行为)的特性,而不是旧版本中硬编码的行为。特性是在文件、消息、字段、枚举等上的选项,用于指定 protoc、代码生成器和 protobuf 运行时的行为。当您的需求与您选择的版本的默认行为不符时,您可以在这些不同的级别(文件、消息、字段……)显式覆盖行为。您还可以覆盖您的覆盖。本主题后面关于词法作用域的部分将详细介绍这一点。

最新发布的版本是 2023。

特性的生命周期

版本为特性的生命周期提供了基本增量。特性具有预期的生命周期:引入它、更改其默认行为、弃用它,然后删除它。例如

  1. 版本 2031 创建了 feature.amazing_new_feature,其默认值为 false。此值保持与所有早期版本相同的行为。也就是说,它默认为没有影响。

  2. 开发人员将其 .proto 文件更新为 edition = "2031"

  3. 之后的版本,例如版本 2033,将 feature.amazing_new_feature 的默认值从 false 切换到 true。这是所有 proto 的期望行为,也是 protobuf 团队创建此特性的原因。

    使用 Prototiller 工具将早期版本的 proto 文件迁移到版本 2033 会根据需要添加显式的 feature.amazing_new_feature = false 条目以继续保留以前的行为。开发人员在希望新行为应用于其 .proto 文件时,会删除这些新添加的设置。

  1. 在某些时候,feature.amazing_new_feature 在某个版本中被标记为已弃用,并在之后的版本中被删除。

    当一个特性被删除时,用于该行为的代码生成器和支持它的运行时库也可能会被删除。不过,时间安排会很宽裕。遵循前面步骤中生命周期的示例,弃用可能会发生在版本 2034,但直到版本 2036(大约两年后)才会被删除。删除一个特性将始终启动一个主要版本更新。

由于这个生命周期,任何不使用已弃用特性的 .proto 文件都可以从一个版本无缝升级到下一个版本。您将拥有完整的 Google 迁移窗口以及弃用窗口来升级您的代码。

前面的生命周期示例对特性使用了布尔值,但特性也可以使用枚举。例如,features.field_presence 具有值 LEGACY_REQUIREDEXPLICITIMPLICIT

迁移到 Protobuf 版本

版本不会破坏现有的二进制文件,也不会更改消息的二进制、文本或 JSON 序列化格式。第一个版本尽可能地减少了破坏性。第一个版本建立了基线,并将 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;
  // 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 = "2023";

package com.example;

message Player {
  // fields have explicit presence, so no explicit setting needed
  string name = 1;
  // 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];

  enum Handed {
    // this overrides the default edition 2023 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 = "2023";

package com.example;

message Player {
  // fields have explicit presence, so no explicit setting needed
  string name = 1;
  // 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];

  enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  Handed handed = 4;

  reserved gender;
}

词法作用域

版本语法支持词法作用域,并提供每个特性的允许目标列表。例如,在第一个版本中,特性只能在文件级别或最低粒度级别指定。词法作用域的实现使您能够为整个文件设置特性的默认行为,然后在消息、字段、枚举、枚举值、oneof、服务或方法级别覆盖该行为。在较高级别(文件、消息)进行的设置在同一作用域(字段、枚举值)内未进行任何设置时适用。任何未显式设置的特性都符合用于 .proto 文件的版本中定义的行为。

以下代码示例显示了一些在文件、字段和枚举级别设置的特性。设置在突出显示的行中

edition = "2023";

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;如果未设置,它将默认为 EXPLICITPay_Type enum 将为 CLOSED,因为它应用了文件级设置。但是,Employment enum 将为 OPEN,因为它是在枚举内设置的。

Prototiller

目前,所有转换为版本格式的操作都由 Protobuf 团队处理。

当此操作转变为自助服务模式时,我们将提供迁移指南和迁移工具来简化迁移到版本以及版本之间的迁移。该工具称为 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 = "2023";

import "myproject/foo.proto";

虽然当您从 proto2 或 proto3 迁移到版本时,生成的代码会发生变化,但线格式不会发生变化。您仍然可以使用基于版本语法的 proto 定义访问 proto2 和 proto3 数据文件或文件流。

语法更改

与 proto2 和 proto3 相比,版本中有一些语法更改。

**语法描述。**您使用 edition 元素而不是 syntax 元素

syntax = "proto2";
syntax = "proto3";
edition = "2028";

**保留名称。**在保留字段名称和枚举值名称时,您不再需要将它们放在引号中

reserved foo, bar;

**组语法。**版本中删除了 proto2 中可用的组语法。组使用的特殊线格式仍然可以通过使用 DELIMITED 消息编码获得。

**必需标签。**版本中不提供仅在 proto2 中可用的 required 标签。可以通过使用 features.field_presence=LEGACY_REQUIRED 来获得底层功能(但不建议)。