Protobuf Editionsの概要
Protobuf Editionsは、Protocol Buffersで使用してきたproto2およびproto3の指定に代わるものです。proto定義ファイルの先頭にsyntax = "proto2"
またはsyntax = "proto3"
を追加する代わりに、edition = "2024"
のようなエディション番号を使用して、ファイルが持つデフォルトの動作を指定します。Editionsを使用すると、言語を時間をかけて段階的に進化させることができます。
以前のバージョンが持っていたハードコードされた動作の代わりに、Editionsは、機能ごとにデフォルト値(動作)を持つ機能のコレクションを表します。機能は、protoc、コードジェネレーター、およびprotobufランタイムの動作を指定する、ファイル、メッセージ、フィールド、列挙型などのオプションです。選択したエディションのデフォルトの動作がニーズに合わない場合、これらの異なるレベル(ファイル、メッセージ、フィールドなど)で動作を明示的にオーバーライドできます。オーバーライドをさらにオーバーライドすることもできます。このトピックの後半にあるレキシカルスコープに関するセクションで、これについて詳しく説明します。
最新のリリースエディションは2023です。
このトピックの例ではエディション2024の機能を示していますが、エディション2024は現在**プレリリースレビュー中**であり、まだプロダクションコードには推奨されていません。
機能のライフサイクル
Editionsは、機能のライフサイクルに対する基本的な増分を提供します。機能には、導入、デフォルトの動作の変更、非推奨化、および削除という予想されるライフサイクルがあります。例:
エディション2031では、
feature.amazing_new_feature
がデフォルト値false
で作成されます。この値は、以前のすべてのエディションと同じ動作を維持します。つまり、デフォルトでは影響はありません。すべての新機能がno-opオプションにデフォルト設定されるわけではありませんが、この例ではamazing_new_feature
はno-opにデフォルト設定されます。開発者は、.protoファイルを
edition = "2031"
に更新します。後のエディション、たとえばエディション2033では、
feature.amazing_new_feature
のデフォルトがfalse
からtrue
に切り替わります。これがすべてのプロトにとって望ましい動作であり、protobufチームがこの機能を作成した理由です。Prototillerツールを使用して、以前のバージョンのprotoファイルをエディション2033に移行すると、以前の動作を保持し続けるために、必要に応じて明示的な
feature.amazing_new_feature = false
エントリが追加されます。開発者は、新しい動作を.protoファイルに適用したいときに、これらの新しく追加された設定を削除します。
ある時点で、
feature.amazing_new_feature
はエディションで非推奨とマークされ、後のエディションで削除されます。機能が削除されると、その動作のコードジェネレーターとそれをサポートするランタイムライブラリも削除される可能性があります。ただし、タイムラインは十分に余裕を持って設定されます。ライフサイクルの前のステップの例に従うと、非推奨化はエディション2034で発生する可能性がありますが、約2年後のエディション2036まで削除されない可能性があります。機能の削除は、常にメジャーバージョンのバンプを開始します。
Googleの移行期間全体と非推奨期間をすべて使ってコードをアップグレードすることができます。
前述のライフサイクルの例では機能にブール値を使用しましたが、機能には列挙型を使用することもできます。たとえば、features.field_presence
にはLEGACY_REQUIRED
、EXPLICIT
、IMPLICIT
という値があります。
Protobuf Editionsへの移行
Editionsは既存のバイナリを破損せず、メッセージのバイナリ、テキスト、またはJSONシリアル化形式を変更しません。エディション2023は、可能な限り最小限の破壊となるように設計されました。それはベースラインを確立し、proto2とproto3の定義を新しい単一の定義形式に統合しました。
より多くのエディションがリリースされるにつれて、機能のデフォルトの動作が変更される可能性があります。Prototillerに.protoファイルのno-op変換を実行させることも、新しい動作の一部またはすべてを受け入れることもできます。Editionsは年に1回程度のリリースが予定されています。
Proto2からEditionsへ
このセクションでは、proto2ファイルと、Prototillerツールを実行して定義ファイルをProtobuf Editions構文を使用するように変更した後の状態を示します。
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";
}
Editions構文
// 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からEditionsへ
このセクションでは、proto3ファイルと、Prototillerツールを実行して定義ファイルをProtobuf Editions構文を使用するように変更した後の状態を示します。
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構文
// 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;
}
レキシカルスコープ
Editions構文はレキシカルスコープをサポートしており、機能ごとに許可されるターゲットのリストがあります。たとえば、エディション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;
}
前の例では、プレゼンス機能はIMPLICIT
に設定されています。設定されていなければ、デフォルトでEXPLICIT
になります。Pay_Type
enum
はファイルレベルの設定を適用するため、CLOSED
になります。ただし、Employment
enum
は、enum内で設定されているため、OPEN
になります。
Prototiller
Prototillerツールがリリースされる際、エディションへの移行とエディション間の移行を容易にするための移行ガイドと移行ツールを提供します。このツールを使用すると、次のことが可能になります。
- proto2およびproto3定義ファイルを新しいエディション構文に大規模に変換する
- ファイルをあるエディションから別のエディションに移行する
- その他の方法でprotoファイルを操作する
下位互換性
Protobuf Editionsは、可能な限り最小限の破壊となるように構築されています。たとえば、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
メッセージエンコーディングを使用することで引き続き利用できます。
必須ラベル
proto2でのみ利用可能だったrequired
ラベルは、エディションでは利用できません。基になる機能は、features.field_presence=LEGACY_REQUIRED
を使用することで引き続き利用できます。
import option
エディション2024では、import option
構文を使用したオプションのインポートがサポートされるようになりました。
オプションのインポートは、他のimport
ステートメントの後に記述する必要があります。
通常のimport
ステートメントとは異なり、オプションのインポートは、他のシンボルをインポートすることなく、.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
}
オプションのインポートは、そのシンボルに対して生成されたコードを必要としないため、deps
の代わりにproto_library
のoption_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
とWeak Field Option
エディション2024以降、weak importは許可されなくなりました。
以前にC++とGoの生成されたコードなしでカスタムオプションをインポートするために「弱い依存関係」を宣言する目的でimport weak
に依存していた場合は、代わりに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機能を使用してください。