Protobuf 版本概覽
Protobuf 版本取代了我們用於 Protocol Buffers 的 proto2 和 proto3 標記。你不再需要在 proto 定義檔案的頂部新增 syntax = "proto2" 或 syntax = "proto3",而是使用版本號,例如 edition = "2024",來指定檔案將具有的預設行為。版本使語言能夠隨著時間的推移而增量演進。
與舊版本中硬編碼的行為不同,版本代表了一系列特性,每個特性都有一個預設值(行為)。特性是檔案、訊息、欄位、列舉等上的選項,它們指定了 protoc、程式碼生成器和 protobuf 執行時的行為。當你的需求與所選版本的預設行為不匹配時,你可以在這些不同的級別(檔案、訊息、欄位等)顯式覆蓋行為。你也可以覆蓋你的覆蓋。本主題後面關於詞法作用域的部分將對此進行更詳細的介紹。
注意:最新發布的版本是 2024。
特性生命週期
版本為特性生命週期提供了基本的增量。特性具有預期的生命週期:引入、更改其預設行為、棄用,然後刪除。例如:
2031 版本建立了
feature.amazing_new_feature,預設值為false。此值保持與所有早期版本相同的行為。也就是說,它預設沒有影響。並非所有新特性都將預設為無操作選項,但為了本示例的目的,amazing_new_feature確實如此。開發者將其 .proto 檔案更新為
edition = "2031"。稍後的版本,例如 2033 版本,將
feature.amazing_new_feature的預設值從false切換為true。這是所有 proto 期望的行為,也是 protobuf 團隊建立此特性的原因。使用 Prototiller 工具將早期版本的 proto 檔案遷移到 2033 版本時,會根據需要新增顯式的
feature.amazing_new_feature = false條目,以繼續保留以前的行為。當開發者希望新行為應用於其 .proto 檔案時,他們會刪除這些新新增的設定。
在某個時候,
feature.amazing_new_feature在某個版本中被標記為棄用,並在後續版本中被移除。當一個特性被移除時,該行為的程式碼生成器和支援它的執行時庫也可能被移除。不過,時間表會很寬鬆。按照生命週期早期步驟中的示例,棄用可能發生在 2034 版本,但直到 2036 版本(大約兩年後)才被移除。移除特性總是會引發主版本號的遞增。
你將有完整的 Google 遷移視窗加上棄用視窗來升級你的程式碼。
前面的生命週期示例使用了布林值作為特性,但特性也可以使用列舉。例如,features.field_presence 具有 LEGACY_REQUIRED、EXPLICIT 和 IMPLICIT 值。
遷移到 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;如果未設定,它將預設為 EXPLICIT。Pay_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 關鍵字
export 和 local 關鍵字在 2024 版本中作為匯入符號的符號可見性修飾符新增,其預設行為由features.default_symbol_visibility指定。
這控制了哪些符號可以從其他 proto 檔案匯入,但不影響程式碼生成。
在 2024 版本中,這些預設可以設定在所有 message 和 enum 符號上。但是,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 特性。