Protobuf Editions の概要
Protobuf Editions は、Protocol Buffers でこれまで使用されてきた proto2 および proto3 の指定を置き換えるものです。proto 定義ファイルの先頭に syntax = "proto2" または syntax = "proto3" を追加する代わりに、edition = "2024" のようなエディション番号を使用して、ファイルが持つデフォルトの動作を指定します。Editions により、言語は時間の経過とともに段階的に進化できるようになります。
古いバージョンが持っていたハードコードされた動作の代わりに、Editions は機能ごとにデフォルト値 (動作) を持つ機能のコレクションを表します。機能とは、ファイル、メッセージ、フィールド、列挙型などに対するオプションであり、protoc、コードジェネレーター、および protobuf ランタイムの動作を指定します。選択したエディションのデフォルトの動作がニーズに合わない場合、それらの異なるレベル (ファイル、メッセージ、フィールドなど) で動作を明示的にオーバーライドできます。オーバーライドをさらにオーバーライドすることも可能です。このトピックの後半にある語彙スコープに関するセクションで、これについて詳しく説明します。
注: 最新のリリースのエディションは 2024 です。
機能のライフサイクル
Editions は、機能のライフサイクルにおける基本的な増加を提供します。機能には、導入、デフォルト動作の変更、非推奨化、削除という予想されるライフサイクルがあります。例:
Edition 2031 は、
feature.amazing_new_featureをデフォルト値falseで作成します。この値は、以前のすべてのエディションと同じ動作を維持します。つまり、影響はありません。すべての新機能が no-op オプションにデフォルト設定されるわけではありませんが、この例のために、amazing_new_featureはそうします。開発者は、.proto ファイルを
edition = "2031"に更新します。Edition 2033 のような後のエディションでは、
feature.amazing_new_featureのデフォルトをfalseからtrueに切り替えます。これはすべてのプロトの望ましい動作であり、protobuf チームがこの機能を作成した理由です。Prototiller ツールを使用して以前のバージョンの proto ファイルを Edition 2033 に移行すると、以前の動作を保持するために、必要に応じて明示的な
feature.amazing_new_feature = falseエントリが追加されます。開発者は、新しい動作を .proto ファイルに適用したいときに、これらの新しく追加された設定を削除します。
ある時点で、
feature.amazing_new_featureはあるエディションで非推奨とマークされ、後のエディションで削除されます。機能が削除されると、その動作のためのコードジェネレーターとそれをサポートするランタイムライブラリも削除される可能性があります。ただし、タイムラインは十分に考慮されます。ライフサイクルの以前のステップの例に従うと、非推奨化は Edition 2034 で発生するかもしれませんが、約 2 年後の Edition 2036 まで削除されないかもしれません。機能の削除は常にメジャーバージョンアップを伴います。
Google 移行の全期間と非推奨期間をコードのアップグレードに利用できます。
上記のライフサイクル例では機能にブール値を使用しましたが、機能は列挙型も使用できます。たとえば、features.field_presence には LEGACY_REQUIRED、EXPLICIT、および IMPLICIT の値があります。
Protobuf Editions への移行
Editions は既存のバイナリを壊すことはなく、メッセージのバイナリ、テキスト、または JSON シリアル化形式を変更することはありません。Edition 2023 は可能な限り最小限の破壊でした。ベースラインを確立し、proto2 と proto3 の定義を新しい単一の定義形式に統合しました。
さらに多くのエディションがリリースされるにつれて、機能のデフォルトの動作が変更される可能性があります。Prototiller に .proto ファイルの no-op 変換を実行させることもできますし、新しい動作の一部またはすべてを受け入れることもできます。Editions はおおよそ年に一度リリースされる予定です。
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 構文は、機能ごとに許可されるターゲットのリストを持つ語彙スコープをサポートしています。たとえば、Edition 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 を使用することで引き続き利用できます。
インポートオプション
Edition 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 の deps の代わりに option_deps として提供する必要があります。これにより、到達不能なコードの生成が回避されます。
proto_library(
name = "foo",
srcs = ["foo.proto"],
option_deps = [":custom_option_proto"]
)
不要なコードの生成を避けるために、protobuf 言語機能やその他のカスタムオプションをインポートする場合は、オプションインポートと option_deps を強く推奨します。
これは、Edition 2024 で削除された import weak を置き換えるものです。
export / local キーワード
export および local キーワードは、Edition 2024 で、features.default_symbol_visibility で指定されたデフォルトの動作から、インポート可能なシンボルのシンボル可視性の修飾子として追加されました。
これは、他の proto ファイルからインポートできるシンボルを制御しますが、コード生成には影響しません。
Edition 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 と弱いフィールドオプション
Edition 2024 以降、弱いインポートは許可されなくなりました。
以前、C++ および Go 用の生成コードなしでカスタムオプションをインポートするために「弱い依存関係」を宣言するために import weak に依存していた場合は、代わりに import option を使用するように移行する必要があります。
詳細については、import option を参照してください。
ctype フィールドオプション
Edition 2024 以降、ctype フィールドオプションは許可されなくなりました。代わりに string_type 機能を使用してください。
詳細については、features.(pb.cpp).string_type を参照してください。
java_multiple_files ファイルオプション
Edition 2024 以降、java_multiple_files ファイルオプションは利用できなくなりました。代わりに、features.(pb.java).nest_in_file_class Java 機能を使用してください。