Protobuf 版本概覽

Protobuf 版本功能的概覽。

Protobuf 版本取代了我們用於 Protocol Buffers 的 proto2 和 proto3 標記。你不再需要在 proto 定義檔案的頂部新增 syntax = "proto2"syntax = "proto3",而是使用版本號,例如 edition = "2024",來指定檔案將具有的預設行為。版本使語言能夠隨著時間的推移而增量演進。

與舊版本中硬編碼的行為不同,版本代表了一系列特性,每個特性都有一個預設值(行為)。特性是檔案、訊息、欄位、列舉等上的選項,它們指定了 protoc、程式碼生成器和 protobuf 執行時的行為。當你的需求與所選版本的預設行為不匹配時,你可以在這些不同的級別(檔案、訊息、欄位等)顯式覆蓋行為。你也可以覆蓋你的覆蓋。本主題後面關於詞法作用域的部分將對此進行更詳細的介紹。

注意:最新發布的版本是 2024。

特性生命週期

版本為特性生命週期提供了基本的增量。特性具有預期的生命週期:引入、更改其預設行為、棄用,然後刪除。例如:

  1. 2031 版本建立了 feature.amazing_new_feature,預設值為 false。此值保持與所有早期版本相同的行為。也就是說,它預設沒有影響。並非所有新特性都將預設為無操作選項,但為了本示例的目的,amazing_new_feature 確實如此。

  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 版本(大約兩年後)才被移除。移除特性總是會引發主版本號的遞增。

你將有完整的 Google 遷移視窗加上棄用視窗來升級你的程式碼。

前面的生命週期示例使用了布林值作為特性,但特性也可以使用列舉。例如,features.field_presence 具有 LEGACY_REQUIREDEXPLICITIMPLICIT 值。

遷移到 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;如果未設定,它將預設為 EXPLICITPay_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 遷移到版本時,生成的程式碼會改變,但傳輸格式不會改變。你仍然可以使用版本語法 proto 定義訪問 proto2 和 proto3 資料檔案或檔案流。

語法變更

與 proto2 和 proto3 相比,版本中存在一些語法變更。

語法描述

你不再使用 syntax 元素,而是使用 edition 元素。

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

保留名稱

在保留欄位名和列舉值名時,你不再需要將它們放在引號中。

reserved foo, bar;

組語法

組語法(在 proto2 中可用)在版本中已移除。組使用的特殊傳輸格式仍然可以透過使用 DELIMITED 訊息編碼來使用。

required 標籤

required 標籤(僅在 proto2 中可用)在版本中不可用。底層功能仍然可以透過使用 features.field_presence=LEGACY_REQUIRED 來實現。

import option

2024 版本增加了對使用 import option 語法匯入選項的支援。

選項匯入必須位於任何其他 import 語句之後。

與普通的 import 語句不同,import option 只匯入在 .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 關鍵字

exportlocal 關鍵字在 2024 版本中作為匯入符號的符號可見性修飾符新增,其預設行為由features.default_symbol_visibility指定。

這控制了哪些符號可以從其他 proto 檔案匯入,但不影響程式碼生成。

在 2024 版本中,這些預設可以設定在所有 messageenum 符號上。但是,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 特性。