言語ガイド (proto 2)

プロジェクトで Protocol Buffers 言語の proto2 リビジョンを使用する方法を説明します。

このガイドでは、プロトコルバッファデータ構造を記述するためのプロトコルバッファ言語の使用方法について説明します。これには、.proto ファイルの構文と、.proto ファイルからデータアクセス クラスを生成する方法が含まれます。プロトコルバッファ言語のproto2リビジョンについて説明します。

エディションの構文については、Protobuf エディション言語ガイドを参照してください。

proto3の構文については、Proto3 言語ガイドを参照してください。

これはリファレンスガイドです。このドキュメントで説明されている多くの機能を使用するステップバイステップの例については、選択した言語のチュートリアルを参照してください。

メッセージタイプの定義

まず、非常に簡単な例を見てみましょう。各検索リクエストにクエリ文字列、関心のある結果の特定のページ、およびページあたりの結果数を含む検索リクエストメッセージ形式を定義したいとします。メッセージタイプを定義するために使用する.protoファイルは次のとおりです。

syntax = "proto2";

message SearchRequest {
  optional string query = 1;
  optional int32 page_number = 2;
  optional int32 results_per_page = 3;
}
  • ファイルの最初の行は、protobuf 言語仕様の proto2 リビジョンを使用していることを示します。

    • syntax は、ファイルの最初の空でない、コメントでない行である必要があります。
    • syntax が指定されていない場合、プロトコルバッファコンパイラは proto2 を使用していると仮定します。
  • SearchRequest メッセージ定義は、このタイプのメッセージに含める各データに対して、3つのフィールド(名前/値のペア)を指定します。各フィールドには名前とタイプがあります。

フィールドタイプの指定

前の例では、すべてのフィールドはスカラー型です。2つの整数(page_numberresults_per_page)と文字列(query)。フィールドには、列挙型や、他のメッセージ型のような複合型を指定することもできます。

フィールド番号の割り当て

メッセージ定義の各フィールドには、1から536,870,911の間の番号を、以下の制限付きで割り当てる必要があります。

  • 与えられた番号は、そのメッセージのすべてのフィールド間で一意でなければなりません
  • フィールド番号19,000から19,999は、Protocol Buffers の実装のために予約されています。これらの予約されたフィールド番号のいずれかをメッセージで使用すると、プロトコルバッファコンパイラは警告を発します。
  • 以前に予約されたフィールド番号や、拡張機能に割り当てられたフィールド番号は使用できません。

この番号は、メッセージワイヤー形式でフィールドを識別するため、メッセージタイプが使用中になった後は変更できません。フィールド番号の「変更」は、そのフィールドを削除し、同じタイプで新しい番号の新しいフィールドを作成することと同じです。適切に行う方法については、フィールドの削除を参照してください。

フィールド番号は決して再利用すべきではありません。新しいフィールド定義のために、予約済みリストからフィールド番号を取り出して再利用しないでください。フィールド番号を再利用することによる影響を参照してください。

最も頻繁に設定されるフィールドには、フィールド番号1から15を使用してください。フィールド番号の値が小さいほど、ワイヤー形式での占有スペースが少なくなります。たとえば、1から15の範囲のフィールド番号はエンコードに1バイトかかります。16から2047の範囲のフィールド番号は2バイトかかります。これについては、Protocol Buffer エンコーディングで詳しく知ることができます。

フィールド番号の再利用による影響

フィールド番号を再利用すると、ワイヤー形式メッセージのデコードが曖昧になります。

protobuf ワイヤー形式は簡潔であり、ある定義を使用してエンコードされ、別の定義を使用してデコードされたフィールドを検出する方法を提供しません。

ある定義を使用してフィールドをエンコードし、その後同じフィールドを別の定義でデコードすると、以下の問題が発生する可能性があります。

  • デバッグによる開発時間の損失
  • 解析/マージエラー (最良のシナリオ)
  • PII/SPII の漏洩
  • データ破損

フィールド番号の再利用の一般的な原因

  • フィールドの番号付けの変更 (フィールドの番号順をより美しくするために行われることがあります)。番号付けを変更すると、関連するすべてのフィールドが実質的に削除され、再追加されるため、互換性のないワイヤー形式の変更が発生します。

  • フィールドを削除し、将来の再利用を防ぐためにその番号を予約しないこと。

    • これは、いくつかの理由により拡張フィールドで非常に簡単に発生する間違いです。拡張宣言は、拡張フィールドを予約するためのメカニズムを提供します。

フィールド番号は32ビットではなく29ビットに制限されています。これは、3ビットがフィールドのワイヤー形式を指定するために使用されるためです。詳細については、エンコーディングのトピックを参照してください。

フィールドのカーディナリティの指定

メッセージフィールドは以下のいずれかになります。

  • 単一:

    proto2 には、単一のフィールドが2種類あります。

    • optional: (推奨) optional フィールドは、次の2つの状態のいずれかになります。

      • フィールドが設定されており、明示的に設定された、またはワイヤーから解析された値を含んでいます。これはワイヤーにシリアライズされます。
      • フィールドが設定されておらず、デフォルト値を返します。これはワイヤーにシリアライズされません。

      値が明示的に設定されたかどうかを確認できます。

    • required: 使用しないでください。 必須フィールドは非常に問題が多く、proto3 およびエディションから削除されました。必須フィールドのセマンティクスはアプリケーション層で実装されるべきです。使用される場合、適切な形式のメッセージには、このフィールドが厳密に1つだけ存在する必要があります。

  • repeated: このフィールドタイプは、適切な形式のメッセージ内で0回以上繰り返すことができます。繰り返された値の順序は保持されます。

  • map: これはキー/値のペアのフィールドタイプです。このフィールドタイプの詳細については、マップを参照してください。

新しい繰り返しフィールドにはパックエンコーディングを使用する

歴史的な理由により、スカラー数値型(例えば、int32int64enum)のrepeatedフィールドは、本来の効率的な方法でエンコードされていません。新しいコードでは、より効率的なエンコーディングを得るために、特別なオプション[packed = true]を使用すべきです。例えば

repeated int32 samples = 4 [packed = true];
repeated ProtoEnum results = 5 [packed = true];

packed エンコーディングの詳細については、Protocol Buffer エンコーディングを参照してください。

Required は強く非推奨

必須フィールドの2つ目の問題は、誰かが enum に値を追加したときに発生します。この場合、認識されない enum 値は欠落しているかのように扱われ、必須値のチェックも失敗します。

適切な形式のメッセージ

「適切な形式 (well-formed)」という用語は、protobuf メッセージに適用される場合、シリアライズ/デシリアライズされたバイトを指します。protoc パーサーは、与えられた proto 定義ファイルが解析可能であることを検証します。

単一フィールドは、ワイヤー形式のバイトに複数回出現する可能性があります。パーサーは入力を受け入れますが、生成されたバインディングを通じてアクセスできるのは、そのフィールドの最後のインスタンスのみです。このトピックの詳細については、「Last One Wins」を参照してください。

その他のメッセージタイプの追加

単一の.protoファイル内で複数のメッセージタイプを定義できます。これは、複数の関連メッセージを定義する場合に便利です。たとえば、SearchResponseメッセージタイプに対応する応答メッセージ形式を定義したい場合、同じ.protoファイルに追加できます。

message SearchRequest {
  optional string query = 1;
  optional int32 page_number = 2;
  optional int32 results_per_page = 3;
}

message SearchResponse {
 ...
}

メッセージの結合は肥大化につながる 単一の.protoファイル内で複数のメッセージタイプ(メッセージ、enum、サービスなど)を定義できますが、依存関係が異なる多数のメッセージが単一のファイルで定義されている場合、依存関係の肥大化につながる可能性もあります。.protoファイルあたりのメッセージタイプは可能な限り少なくすることをお勧めします。

コメントの追加

.proto ファイルにコメントを追加するには

  • .proto コード要素の前の行には、C/C++/Java の行末スタイルコメント「//」を使用することを推奨します。

  • C スタイルのインライン/複数行コメント /* ... */ も受け入れられます。

    • 複数行コメントを使用する場合は、「*」のマージンラインが推奨されます。
/**
 * SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response.
 */
message SearchRequest {
  optional string query = 1;

  // Which page number do we want?
  optional int32 page_number = 2;

  // Number of results to return per page.
  optional int32 results_per_page = 3;
}

フィールドの削除

フィールドの削除は、適切に行わないと深刻な問題を引き起こす可能性があります。

required フィールドは削除しないでください。これを安全に行うことはほとんど不可能です。required フィールドを削除する必要がある場合は、まずそのフィールドをoptionalおよびdeprecatedとしてマークし、メッセージを何らかの形で監視するすべてのシステムが新しいスキーマで展開されていることを確認する必要があります。その後、フィールドの削除を検討できます(ただし、これは依然としてエラーが発生しやすいプロセスであることに注意してください)。

required でないフィールドが不要になった場合は、まずクライアントコードからそのフィールドへのすべての参照を削除し、次にメッセージからフィールド定義を削除します。ただし、削除されたフィールド番号は必ず予約してください。フィールド番号を予約しないと、将来的に開発者がその番号を再利用して破損を引き起こす可能性があります。

メッセージの JSON および TextFormat エンコーディングが引き続き解析できるように、フィールド名も予約する必要があります。

予約済みフィールド番号

メッセージタイプをフィールドの完全な削除またはコメントアウトによって更新した場合、将来の開発者はそのタイプを更新する際にそのフィールド番号を再利用することができます。これにより、フィールド番号の再利用による影響で説明されているように、深刻な問題を引き起こす可能性があります。これを防ぐために、削除したフィールド番号をreservedリストに追加してください。

将来の開発者がこれらの予約済みフィールド番号を使用しようとすると、protoc コンパイラはエラーメッセージを生成します。

message Foo {
  reserved 2, 15, 9 to 11;
}

予約済みフィールド番号の範囲は包括的です (9から119, 10, 11と同じです)。

予約済みフィールド名

古いフィールド名を後で再利用することは一般的に安全ですが、フィールド名がシリアライズされる TextProto または JSON エンコーディングを使用する場合は例外です。このリスクを避けるために、削除したフィールド名をreservedリストに追加できます。

予約名は protoc コンパイラの動作のみに影響し、実行時動作には影響しませんが、1つの例外があります。TextProto の実装では、解析時に予約名を持つ未知のフィールドを破棄する場合があります(他の未知のフィールドのようにエラーを発生させずに。現在、C++ と Go の実装のみがこれを行います)。実行時の JSON 解析は予約名の影響を受けません。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

同じreservedステートメント内でフィールド名とフィールド番号を混在させることはできません。

.proto ファイルから何が生成されるか?

.proto ファイルに対してプロトコルバッファコンパイラを実行すると、コンパイラは、ファイルで記述したメッセージ型を操作するために必要な選択した言語のコードを生成します。これには、フィールド値の取得と設定、メッセージの出力ストリームへのシリアライズ、入力ストリームからのメッセージの解析が含まれます。

  • C++の場合、コンパイラは各.protoファイルから.hファイルと.ccファイルを生成し、ファイルで記述された各メッセージ型に対応するクラスを作成します。
  • Javaの場合、コンパイラは各メッセージタイプに対応するクラスと、メッセージクラスインスタンスを作成するための特別なBuilderクラスを含む.javaファイルを生成します。
  • Kotlinの場合、Java で生成されたコードに加えて、コンパイラは各メッセージ型に対して改善された Kotlin API を持つ.ktファイルを生成します。これには、メッセージインスタンスの作成を簡素化する DSL、nullable フィールドアクセサー、およびコピー関数が含まれます。
  • Pythonは少し異なります。Python コンパイラは、.proto内の各メッセージ型の静的記述子を持つモジュールを生成し、これは実行時に必要な Python データアクセス クラスを作成するためにメタクラスと共に使用されます。
  • Goの場合、コンパイラはファイル内の各メッセージタイプに対応する型を持つ.pb.goファイルを生成します。
  • Rubyの場合、コンパイラはメッセージ型を含む Ruby モジュールを持つ.rbファイルを生成します。
  • Objective-Cの場合、コンパイラは各.protoファイルからpbobjc.hおよびpbobjc.mファイルを生成し、ファイルで記述された各メッセージタイプに対応するクラスを作成します。
  • C#の場合、コンパイラは各.protoファイルから.csファイルを生成し、ファイルで記述された各メッセージタイプに対応するクラスを作成します。
  • PHPの場合、コンパイラはファイルで記述された各メッセージタイプに対応する.phpメッセージファイルと、コンパイルする各.protoファイルに対応する.phpメタデータファイルを生成します。メタデータファイルは、有効なメッセージタイプをディスクリプタプールにロードするために使用されます。
  • Dartの場合、コンパイラはファイル内の各メッセージタイプに対応するクラスを持つ.pb.dartファイルを生成します。

各言語の API の使用方法の詳細については、選択した言語のチュートリアルを参照してください。API の詳細については、関連するAPI リファレンスを参照してください。

スカラー値タイプ

スカラーメッセージフィールドは次のいずれかの型を持つことができます。以下の表は、.protoファイルで指定された型と、自動生成されたクラスでの対応する型を示しています。

Proto 型備考
double
float
int32可変長エンコーディングを使用します。負の数のエンコーディングには非効率的です。フィールドが負の値を持ちそうな場合は、代わりに sint32 を使用してください。
int64可変長エンコーディングを使用します。負の数のエンコーディングには非効率的です。フィールドが負の値を持ちそうな場合は、代わりに sint64 を使用してください。
uint32可変長エンコーディングを使用します。
uint64可変長エンコーディングを使用します。
sint32可変長エンコーディングを使用します。符号付き整数値。通常の int32 よりも効率的に負の数をエンコードします。
sint64可変長エンコーディングを使用します。符号付き整数値。通常の int64 よりも効率的に負の数をエンコードします。
fixed32常に4バイト。値が228より大きいことが多い場合、uint32 よりも効率的です。
fixed64常に8バイト。値が256より大きいことが多い場合、uint64 よりも効率的です。
sfixed32常に4バイト。
sfixed64常に8バイト。
bool
string文字列は常に UTF-8 エンコードまたは7ビット ASCII テキストを含む必要があり、232より長くすることはできません。
bytes232を超えない任意のバイトシーケンスを含むことができます。
Proto 型C++ 型Java/Kotlin 型[1]Python 型[3]Go 型Ruby 型C# 型PHP 型Dart 型Rust 型
doubledoubledoublefloat*float64Floatdoublefloatdoublef64
floatfloatfloatfloat*float32Floatfloatfloatdoublef32
int32int32_tintintint32Fixnum または Bignum (必要に応じて)intinteger*int32i32
int64int64_tlongint/long[4]*int64Bignumlonginteger/string[6]Int64i64
uint32uint32_tint[2]int/long[4]*uint32Fixnum または Bignum (必要に応じて)uintintegerintu32
uint64uint64_tlong[2]int/long[4]*uint64Bignumulonginteger/string[6]Int64u64
sint32int32_tintintint32Fixnum または Bignum (必要に応じて)intinteger*int32i32
sint64int64_tlongint/long[4]*int64Bignumlonginteger/string[6]Int64i64
fixed32uint32_tint[2]int/long[4]*uint32Fixnum または Bignum (必要に応じて)uintintegerintu32
fixed64uint64_tlong[2]int/long[4]*uint64Bignumulonginteger/string[6]Int64u64
sfixed32int32_tintint*int32Fixnum または Bignum (必要に応じて)intintegerinti32
sfixed64int64_tlongint/long[4]*int64Bignumlonginteger/string[6]Int64i64
boolboolbooleanbool*boolTrueClass/FalseClassboolbooleanboolbool
stringstringStringunicode (Python 2), str (Python 3)*stringString (UTF-8)stringstringStringProtoString
bytesstringByteStringbytes[]byteString (ASCII-8BIT)ByteStringstringListProtoBytes

[1] Kotlin は、混合 Java/Kotlin コードベースでの互換性を確保するため、符号なし型であっても Java の対応する型を使用します。

[2] Java では、符号なし32ビットおよび64ビット整数は、符号付きの対応する型を使用して表現され、最上位ビットは単に符号ビットに格納されます。

[3] いずれの場合も、フィールドに値を設定する際には、その値が有効であることを確認するために型チェックが行われます。

[4] 64ビットまたは符号なし32ビット整数は、デコード時には常に long として表現されますが、フィールドを設定する際に int が指定された場合は int になることもあります。いずれの場合も、設定時には値が表現される型に収まる必要があります。[2]を参照してください。

[5] Proto2 は通常、文字列フィールドの UTF-8 の有効性をチェックすることはありません。ただし、言語によって動作は異なり、無効な UTF-8 データは文字列フィールドに保存すべきではありません。

[6] 64ビットマシンでは Integer が使用され、32ビットマシンでは string が使用されます。

メッセージをシリアライズする際にこれらの型がどのようにエンコードされるかの詳細については、Protocol Buffer エンコーディングを参照してください。

デフォルトフィールド値

メッセージが解析される際、エンコードされたメッセージのバイトに特定のフィールドが含まれていない場合、解析されたオブジェクト内のそのフィールドにアクセスすると、そのフィールドのデフォルト値が返されます。デフォルト値は型に固有です。

  • 文字列の場合、デフォルト値は空の文字列です。
  • バイトの場合、デフォルト値は空のバイトです。
  • ブール型の場合、デフォルト値は false です。
  • 数値型の場合、デフォルト値はゼロです。
  • メッセージフィールドの場合、フィールドは設定されていません。その正確な値は言語に依存します。詳細については、お使いの言語の生成コードガイドを参照してください。
  • enums の場合、デフォルト値は最初に定義された enum 値であり、これは 0 であるべきです(オープン enums との互換性のために推奨)。Enum のデフォルト値を参照してください。

繰り返しフィールドのデフォルト値は空です (通常、適切な言語では空のリスト)。

マップフィールドのデフォルト値は空です (通常、適切な言語では空のマップ)。

デフォルトスカラー値のオーバーライド

proto2 では、単一の非メッセージフィールドに対して明示的なデフォルト値を指定できます。たとえば、SearchRequest.results_per_pageフィールドにデフォルト値10を設定したいとします。

optional int32 results_per_page = 3 [default = 10];

送信者がresults_per_pageを指定しない場合、受信者は以下の状態を観測します。

  • results_per_pageフィールドは存在しません。つまり、has_results_per_page()(ハズラーメソッド)はfalseを返します。
  • results_per_page の値(「getter」から返される値)は10です。

送信者がresults_per_pageの値を送信した場合、デフォルト値の10は無視され、送信者の値が「ゲッター」から返されます。

生成されたコードにおけるデフォルト値の動作の詳細については、選択した言語の生成コードガイドを参照してください。

enums のデフォルト値は、最初に定義された enum 値であるため、enum 値リストの先頭に値を追加する際には注意してください。定義を安全に変更する方法のガイドラインについては、メッセージタイプの更新セクションを参照してください。

列挙型

メッセージタイプを定義するとき、そのフィールドの1つが事前定義された値のリストのいずれかのみを持つようにしたい場合があります。たとえば、各SearchRequestcorpusフィールドを追加したいとします。ここで、コーパスはUNIVERSALWEBIMAGESLOCALNEWSPRODUCTS、またはVIDEOになります。これを非常に簡単に行うには、各可能な値の定数を持つenumをメッセージ定義に追加します。

以下の例では、可能なすべての値を持つCorpusという名前のenumと、Corpus型のフィールドを追加しました。

enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}

message SearchRequest {
  optional string query = 1;
  optional int32 page_number = 2;
  optional int32 results_per_page = 3;
  optional Corpus corpus = 4;
}

Enum のデフォルト値

SearchRequest.corpusフィールドのデフォルト値はCORPUS_UNSPECIFIEDです。これは、enum で定義された最初の値だからです。

すべての enum の最初の値をENUM_TYPE_NAME_UNSPECIFIED = 0;またはENUM_TYPE_NAME_UNKNOWN = 0;と定義することを強くお勧めします。これは、proto2 が enum フィールドの不明な値を処理する方法に起因します。

また、この最初のデフォルト値は、「この値は未指定であった」以外のセマンティックな意味を持たないことが推奨されます。

SearchRequest.corpusのような enum フィールドのデフォルト値は、このように明示的にオーバーライドできます。

  optional Corpus corpus = 4 [default = CORPUS_UNIVERSAL];

Enum 値のエイリアス

異なる enum 定数に同じ値を割り当てることで、エイリアスを定義できます。これを行うには、allow_alias オプションをtrueに設定する必要があります。そうしないと、エイリアスが見つかった場合にプロトコルバッファコンパイラが警告メッセージを生成します。すべてのエイリアス値はシリアライゼーションでは有効ですが、デシリアライゼーションでは最初の値のみが使用されます。

enum EnumAllowingAlias {
  option allow_alias = true;
  EAA_UNSPECIFIED = 0;
  EAA_STARTED = 1;
  EAA_RUNNING = 1;
  EAA_FINISHED = 2;
}

enum EnumNotAllowingAlias {
  ENAA_UNSPECIFIED = 0;
  ENAA_STARTED = 1;
  // ENAA_RUNNING = 1;  // Uncommenting this line will cause a warning message.
  ENAA_FINISHED = 2;
}

列挙子定数は32ビット整数の範囲内である必要があります。enum値はワイヤー上でvarint エンコーディングを使用するため、負の値は非効率的であり、推奨されません。前の例のように、メッセージ定義内でenumを定義することも、外側で定義することもできます。これらのenumは、.protoファイル内の任意のメッセージ定義で再利用できます。また、あるメッセージで宣言されたenum型を、_MessageType_._EnumType_の構文を使用して、別のメッセージのフィールドの型として使用することもできます。

enum を使用する.proto に対してプロトコルバッファコンパイラを実行すると、生成されたコードには、Java、Kotlin、または C++ 用の対応するenum、あるいは実行時に生成されたクラス内で整数値を持つシンボリック定数のセットを作成するために使用される Python 用の特別なEnumDescriptorクラスが用意されます。

enum 値を削除することは、永続化されたプロトに対して破壊的な変更となります。値を削除する代わりに、その値をreservedキーワードでマークして、enum 値がコード生成されないようにするか、または値を保持しつつ、deprecatedフィールドオプションを使用して後で削除されることを示します。

enum PhoneType {
  PHONE_TYPE_UNSPECIFIED = 0;
  PHONE_TYPE_MOBILE = 1;
  PHONE_TYPE_HOME = 2;
  PHONE_TYPE_WORK = 3 [deprecated=true];
  reserved 4,5;
}

アプリケーションでメッセージenumを操作する方法の詳細については、選択した言語の生成コードガイドを参照してください。

予約値

enum 型を完全に enum エントリを削除するか、コメントアウトして更新した場合、将来のユーザーは独自の型への更新を行う際にその数値値を再利用する可能性があります。これにより、同じ.protoの古いインスタンスを後でロードした場合に、データ破損、プライバシーバグなど、深刻な問題が発生する可能性があります。これを防ぐ1つの方法は、削除したエントリの数値(および/またはJSONシリアライゼーションでも問題を引き起こす可能性のある名前)をreservedと指定することです。将来のユーザーがこれらの識別子を使用しようとすると、プロトコルバッファコンパイラは警告を発します。予約する数値の範囲が最大値に達することをmaxキーワードを使用して指定できます。

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

同じreservedステートメント内でフィールド名と数値が混在できないことに注意してください。

他のメッセージタイプの使用

他のメッセージタイプをフィールドタイプとして使用できます。たとえば、各SearchResponseメッセージにResultメッセージを含めたいとします。これを行うには、同じ.protoファイル内でResultメッセージタイプを定義し、次にSearchResponse内にResult型のフィールドを指定します。

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  optional string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}

定義のインポート

先の例では、ResultメッセージタイプはSearchResponseと同じファイルで定義されています。もし、フィールドタイプとして使用したいメッセージタイプが別の.protoファイルで既に定義されている場合はどうなるでしょうか?

他の.protoファイルからの定義は、それらをインポートすることで使用できます。別の.protoの定義をインポートするには、ファイルの先頭にインポートステートメントを追加します。

import "myproject/other_protos.proto";

デフォルトでは、直接インポートされた.protoファイルからの定義のみを使用できます。しかし、.protoファイルを新しい場所に移動する必要がある場合があります。.protoファイルを直接移動して、すべての呼び出し箇所を1つの変更で更新する代わりに、古い場所にプレースホルダーの.protoファイルを置き、import publicの概念を使用してすべてのインポートを新しい場所に転送できます。

パブリックインポート機能は、Java、Kotlin、TypeScript、JavaScript、GCL、および protobuf 静的リフレクションを使用する C++ ターゲットでは利用できません。

import publicの依存関係は、import publicステートメントを含むプロトをインポートするすべてのコードによって推移的に利用できます。例:

// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

プロトコルコンパイラは、-I/--proto_pathフラグを使用してプロトコルコンパイラのコマンドラインで指定されたディレクトリセットからインポートされたファイルを検索します。フラグが指定されていない場合、コンパイラが呼び出されたディレクトリを検索します。一般的には、--proto_pathフラグをプロジェクトのルートに設定し、すべてのインポートに完全修飾名を使用する必要があります。

proto3 メッセージタイプの使用

proto3 および 2023 エディションのメッセージタイプをインポートして、proto2 メッセージで使用したり、その逆も可能です。ただし、proto2 の enum は proto3 構文で直接使用することはできません(インポートされた proto2 メッセージがそれらを使用する場合は問題ありません)。

ネストされた型

他のメッセージ型の中にメッセージ型を定義して使用することができます。以下の例のように、ここではResultメッセージがSearchResponseメッセージの中に定義されています。

message SearchResponse {
  message Result {
    optional string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

このメッセージタイプを親メッセージタイプの外側で再利用したい場合は、_Parent_._Type_として参照します。

message SomeOtherMessage {
  optional SearchResponse.Result result = 1;
}

メッセージは好きなだけ深くネストできます。以下の例では、Innerという名前の2つのネストされた型は、異なるメッセージ内で定義されているため、完全に独立していることに注意してください。

message Outer {       // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      optional int64 ival = 1;
      optional bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      optional int32  ival = 1;
      optional bool   booly = 2;
    }
  }
}

グループ

グループ機能は非推奨であり、新しいメッセージ型を作成する際には使用すべきではありません。代わりにネストされたメッセージ型を使用してください。

グループは、メッセージ定義に情報をネストする別の方法です。たとえば、複数のResultを含むSearchResponseを指定する別の方法は次のとおりです。

message SearchResponse {
  repeated group Result = 1 {
    optional string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
}

グループは、ネストされたメッセージ型とフィールドを単一の宣言に結合するだけです。コード内では、このメッセージをresultという名前のResult型フィールドを持っているかのように扱うことができます(後者の名前は、前者と競合しないように小文字に変換されます)。したがって、この例は、メッセージのワイヤー形式が異なる点を除いて、以前のSearchResponseとまったく同じです。

メッセージタイプの更新

既存のメッセージタイプがすべてのニーズを満たさなくなった場合(たとえば、メッセージ形式に余分なフィールドを追加したい場合)でも、古い形式で作成されたコードを使用したい場合は、心配いりません!バイナリワイヤー形式を使用すれば、既存のコードを壊すことなくメッセージタイプを更新することは非常に簡単です。

Proto のベストプラクティスと以下のルールを確認してください。

  • 既存のフィールドのフィールド番号を変更しないでください。フィールド番号を「変更する」ことは、そのフィールドを削除し、同じ型で新しいフィールドを追加することと同じです。フィールドの番号を変更したい場合は、フィールドを削除するの手順を参照してください。
  • 追加する新しいフィールドは、optionalまたはrepeatedである必要があります。これは、「古い」メッセージ形式を使用するコードによってシリアライズされたメッセージでも、required要素が欠落しないため、新しい生成コードで解析できることを意味します。新しいコードが古いコードによって生成されたメッセージと適切にやり取りできるように、これらの要素のデフォルト値を考慮する必要があります。同様に、新しいコードによって作成されたメッセージは古いコードで解析できます。古いバイナリは解析時に新しいフィールドを単に無視します。ただし、未知のフィールドは破棄されず、後でメッセージがシリアライズされる場合、未知のフィールドも一緒にシリアライズされます。したがって、メッセージが新しいコードに渡された場合でも、新しいフィールドは引き続き利用可能です。詳細については、未知のフィールドセクションを参照してください。
  • 非必須フィールドは、更新されたメッセージタイプでそのフィールド番号が再利用されない限り、削除できます。代わりにフィールド名を変更する(例えば「OBSOLETE_」というプレフィックスを追加する)か、フィールド番号を予約済みにして、将来の.protoユーザーが誤ってその番号を再利用しないようにすることができます。
  • 非必須フィールドは、型と番号が同じままであれば、拡張機能に変換することも、その逆も可能です。
  • int32uint32int64uint64、およびboolはすべて互換性があります。これは、これらの型間でフィールドを変更しても、前方互換性も後方互換性も損なわないことを意味します。ワイヤーから解析された数値が対応する型に収まらない場合、C++ でその型にキャストした場合と同じ効果が得られます(たとえば、64ビットの数値が int32 として読み取られた場合、32ビットに切り捨てられます)。
  • sint32sint64 は互いに互換性がありますが、他の整数型とは互換性がありません。書き込まれた値が INT_MIN と INT_MAX の間(両端を含む)であった場合、どちらの型でも同じ値として解析されます。sint64 の値がその範囲外で書き込まれ、sint32 として解析された場合、varint は32ビットに切り捨てられ、その後ジグザグデコードが行われます(これにより異なる値が観測されます)。
  • バイトが有効な UTF-8 である限り、stringbytesは互換性があります。
  • 埋め込みメッセージは、バイトにエンコードされたメッセージのインスタンスが含まれている場合、bytesと互換性があります。
  • fixed32sfixed32と互換性があり、fixed64sfixed64と互換性があります。
  • stringbytes、およびメッセージフィールドの場合、単一型はrepeatedと互換性があります。繰り返しフィールドのシリアライズされたデータが入力として与えられた場合、このフィールドが単一型であると期待するクライアントは、それがプリミティブ型フィールドであれば最後の入力値を取り込み、メッセージ型フィールドであればすべての入力要素をマージします。ただし、これはブール型や enum を含む数値型には一般的に安全ではありません。数値型の繰り返しフィールドは、単一フィールドが期待される場合に正しく解析されないパック形式でシリアライズされることがあります。
  • デフォルト値を変更することは一般的に問題ありませんが、デフォルト値はワイヤー経由で送信されないことを覚えておく必要があります。したがって、プログラムが特定のフィールドが設定されていないメッセージを受信した場合、そのプログラムはプロトコルのそのプログラムのバージョンで定義されたデフォルト値を認識します。送信者のコードで定義されたデフォルト値は認識しません。
  • enum はワイヤー形式の観点からint32uint32int64uint64と互換性があります(値が収まらない場合は切り捨てられます)。ただし、メッセージがデシリアライズされる際にクライアントコードがそれらを異なる方法で扱う可能性があることに注意してください。特に、認識されないenum値はメッセージがデシリアライズされる際に破棄され、そのフィールドのhas..アクセサーは false を返し、そのゲッターはenum定義にリストされている最初の値、またはデフォルト値が指定されている場合はその値を返します。繰り返し enum フィールドの場合、認識されない値はすべてリストから削除されます。しかし、整数フィールドは常にその値を保持します。このため、ワイヤー上で範囲外の enum 値を受信するという点で、整数をenumにアップグレードする際には非常に注意が必要です。
  • 現在の Java および C++ の実装では、認識されないenum値が削除されると、それらは他の未知のフィールドと共に保存されます。このデータがシリアライズされ、その値を認識するクライアントによって再解析された場合、奇妙な動作につながる可能性があることに注意してください。オプションフィールドの場合、元のメッセージがデシリアライズされた後に新しい値が書き込まれたとしても、古い値はそれを認識するクライアントによって引き続き読み取られます。繰り返しフィールドの場合、古い値は認識された新しい値の後に現れるため、順序は保持されません。
  • 単一のoptionalフィールドまたは拡張機能を新しいoneofのメンバーに変更することはバイナリ互換性がありますが、一部の言語(特に Go)では、生成されたコードの API が互換性のない方法で変更されます。このため、Google はAIP-180に記載されているように、そのパブリック API でこのような変更を行いません。ソース互換性に関する同様の注意点として、一度に複数のフィールドを新しいoneofに移動することは、一度に複数のフィールドを設定するコードがないことを確認していれば安全な場合があります。フィールドを既存のoneofに移動することは安全ではありません。同様に、単一フィールドのoneofoptionalフィールドまたは拡張機能に変更することは安全です。
  • map<K, V>と対応するrepeatedメッセージフィールド間でフィールドを変更することはバイナリ互換性があります(メッセージレイアウトおよびその他の制限については、以下のマップを参照)。ただし、変更の安全性はアプリケーションに依存します。メッセージをデシリアライズして再シリアライズする場合、repeatedフィールド定義を使用するクライアントは意味的に同一の結果を生成しますが、mapフィールド定義を使用するクライアントはエントリの順序を変更したり、重複するキーを持つエントリを破棄したりする可能性があります。

未知のフィールド

未知のフィールドとは、パーサーが認識しないフィールドを表す、適切な形式のプロトコルバッファシリアライズデータです。たとえば、古いバイナリが新しいフィールドを持つ新しいバイナリによって送信されたデータを解析する場合、これらの新しいフィールドは古いバイナリでは未知のフィールドとなります。

当初、proto3 メッセージは解析時に常に未知のフィールドを破棄していましたが、バージョン3.5で proto2 の動作に合わせるため、未知のフィールドの保持を再導入しました。バージョン3.5以降では、未知のフィールドは解析中に保持され、シリアライズされた出力に含まれます。

未知のフィールドの保持

一部のアクションは未知のフィールドの損失を引き起こす可能性があります。たとえば、以下のいずれかを実行すると、未知のフィールドは失われます。

  • プロトを JSON にシリアライズする。
  • メッセージ内のすべてのフィールドを繰り返し処理して、新しいメッセージを生成する。

未知のフィールドの損失を避けるには、以下を実行します。

  • バイナリを使用し、データ交換にテキスト形式を使用しないでください。
  • CopyFrom()MergeFrom()などのメッセージ指向 API を使用して、フィールドごとにコピーするのではなく、データをコピーします。

TextFormat はやや特殊なケースです。TextFormat にシリアライズすると、未知のフィールドはフィールド番号を使用して出力されます。しかし、フィールド番号を使用するエントリがある場合、TextFormat データをバイナリ proto に再解析することは失敗します。

拡張機能

拡張機能とは、コンテナメッセージの外部で定義されるフィールドであり、通常はコンテナメッセージの.protoファイルとは別の.protoファイルにあります。

なぜ拡張機能を使用するのか?

拡張機能を使用する主な理由は2つあります。

  • コンテナメッセージの.protoファイルのインポート/依存関係が少なくなります。これにより、ビルド時間の改善、循環依存関係の解消、および疎結合の促進につながります。拡張機能はこれに非常に適しています。
  • システムが、最小限の依存関係と調整でコンテナメッセージにデータを添付できるようにします。拡張機能は、フィールド番号空間が限られていることと、フィールド番号を再利用することによる影響のため、このための優れたソリューションではありません。多数の拡張機能に対して非常に低い調整しか必要としないユースケースの場合は、代わりにAnyメッセージタイプを使用することを検討してください。

拡張機能の例

拡張機能の例を見てみましょう。

// file kittens/video_ext.proto

import "kittens/video.proto";
import "media/user_content.proto";

package kittens;

// This extension allows kitten videos in a media.UserContent message.
extend media.UserContent {
  // Video is a message imported from kittens/video.proto
  repeated Video kitten_videos = 126;
}

拡張機能を定義するファイル(kittens/video_ext.proto)が、コンテナメッセージのファイル(media/user_content.proto)をインポートしていることに注意してください。

コンテナメッセージは、拡張機能のためにフィールド番号の一部を予約する必要があります。

// file media/user_content.proto

package media;

// A container message to hold stuff that a user has created.
message UserContent {
  // Set verification to `DECLARATION` to enforce extension declarations for all
  // extensions in this range.
  extensions 100 to 199 [verification = DECLARATION];
}

コンテナメッセージのファイル(media/user_content.proto)は、拡張機能用にフィールド番号 [100 から 199] を予約するメッセージUserContentを定義します。そのすべての拡張機能に宣言を要求するために、その範囲にverification = DECLARATIONを設定することが推奨されます。

新しい拡張機能(kittens/video_ext.proto)が追加されたら、UserContentにそれに対応する宣言を追加し、verificationを削除する必要があります。

// A container message to hold stuff that a user has created.
message UserContent {
  extensions 100 to 199 [
    declaration = {
      number: 126,
      full_name: ".kittens.kitten_videos",
      type: ".kittens.Video",
      repeated: true
    },
    // Ensures all field numbers in this extension range are declarations.
    verification = DECLARATION
  ];
}

UserContentは、フィールド番号126が完全修飾名.kittens.kitten_videosと完全修飾型.kittens.Videoを持つrepeated拡張フィールドによって使用されることを宣言します。拡張宣言の詳細については、拡張宣言を参照してください。

コンテナメッセージのファイル(media/user_content.proto)は kitten_video 拡張定義(kittens/video_ext.proto)をインポートしないことに注意してください。

拡張フィールドのワイヤー形式エンコーディングは、同じフィールド番号、型、およびカーディナリティを持つ標準フィールドと比較して違いはありません。したがって、フィールド番号、型、およびカーディナリティが一定である限り、標準フィールドをコンテナから拡張機能として移動したり、拡張フィールドをコンテナメッセージに標準フィールドとして移動したりしても安全です。

しかし、拡張機能はコンテナメッセージの外部で定義されているため、特定の拡張フィールドを取得および設定するための特別なアクセサーは生成されません。この例では、protobuf コンパイラはAddKittenVideos()GetKittenVideos()アクセサーを生成しません。代わりに、拡張機能は、HasExtension()ClearExtension()GetExtension()MutableExtension()AddExtension()のようなパラメータ化された関数を通じてアクセスされます。

C++ では、次のようになります。

UserContent user_content;
user_content.AddExtension(kittens::kitten_videos, new kittens::Video());
assert(1 == user_content.GetExtensionCount(kittens::kitten_videos));
user_content.GetExtension(kittens::kitten_videos, 0);

拡張範囲の定義

コンテナメッセージの所有者である場合、メッセージの拡張機能用の拡張範囲を定義する必要があります。

拡張フィールドに割り当てられたフィールド番号は、標準フィールドには再利用できません。

拡張範囲は定義後に拡張しても安全です。良いデフォルトは、比較的小さな番号を1000個割り当て、拡張宣言を使用してその空間を密に埋めることです。

message ModernExtendableMessage {
  // All extensions in this range should use extension declarations.
  extensions 1000 to 2000 [verification = DECLARATION];
}

実際の拡張機能の前に拡張宣言の範囲を追加する際は、この新しい範囲で宣言が使用されることを強制するためにverification = DECLARATIONを追加する必要があります。このプレースホルダーは、実際の宣言が追加されたら削除できます。

既存の拡張範囲を、同じ合計範囲をカバーする別々の範囲に分割することは安全です。これは、レガシーなメッセージ型を拡張宣言に移行するために必要となる場合があります。たとえば、移行前は範囲が次のように定義されているかもしれません。

message LegacyMessage {
  extensions 1000 to max;
}

そして移行後(範囲を分割後)は次のようになります。

message LegacyMessage {
  // Legacy range that was using an unverified allocation scheme.
  extensions 1000 to 524999999 [verification = UNVERIFIED];
  // Current range that uses extension declarations.
  extensions 525000000 to max  [verification = DECLARATION];
}

拡張範囲を移動または縮小するために、開始フィールド番号を増やしたり、終了フィールド番号を減らしたりすることは安全ではありません。これらの変更は、既存の拡張機能を無効にする可能性があります。

プロトのほとんどのインスタンスで設定される標準フィールドには、フィールド番号1から15を使用することを推奨します。これらの番号を拡張機能に使用することは推奨されません。

番号付け規則で拡張機能に非常に大きなフィールド番号が含まれる可能性がある場合、maxキーワードを使用して、拡張範囲が最大可能なフィールド番号まで拡張されるように指定できます。

message Foo {
  extensions 1000 to max;
}

max は 229 - 1、つまり 536,870,911 です。

拡張番号の選択

拡張機能は、コンテナメッセージの外部で指定できる単なるフィールドです。フィールド番号の割り当てに関するすべての同じ規則が拡張フィールド番号にも適用されます。また、フィールド番号の再利用による影響と同じ影響が、拡張フィールド番号の再利用にも適用されます。

コンテナメッセージが拡張宣言を使用している場合、一意の拡張フィールド番号を選択するのは簡単です。新しい拡張機能を定義するときは、コンテナメッセージで定義されている最も高い拡張範囲から、他のすべての宣言よりも低いフィールド番号を選択します。たとえば、コンテナメッセージが次のように定義されている場合、

message Container {
  // Legacy range that was using an unverified allocation scheme
  extensions 1000 to 524999999;
  // Current range that uses extension declarations. (highest extension range)
  extensions 525000000 to max  [
    declaration = {
      number: 525000001,
      full_name: ".bar.baz_ext",
      type: ".bar.Baz"
    }
    // 525,000,002 is the lowest field number above all other declarations
  ];
}

Containerの次の拡張機能は、525000002の番号を持つ新しい宣言を追加する必要があります。

未検証の拡張番号割り当て (非推奨)

コンテナメッセージの所有者は、拡張宣言を放棄し、独自の未検証の拡張番号割り当て戦略を選択する場合があります。

未検証の割り当てスキームは、protobuf エコシステム外部のメカニズムを使用して、選択された拡張範囲内で拡張フィールド番号を割り当てます。例として、モノレポのコミット番号を使用することが考えられます。このシステムは、protobuf コンパイラの観点からは「未検証」です。なぜなら、拡張機能が適切に取得された拡張フィールド番号を使用しているかどうかをチェックする方法がないためです。

未検証のシステムが拡張宣言のような検証済みシステムに比べて持つ利点は、コンテナメッセージの所有者と調整することなく拡張機能を定義できることです。

未検証のシステムの欠点は、protobuf コンパイラが参加者を拡張フィールド番号の再利用から保護できないことです。

未検証の拡張フィールド番号割り当て戦略は推奨されませんフィールド番号を再利用することによる影響が、メッセージのすべての拡張機能に影響を及ぼすためです(推奨事項に従わなかった開発者だけでなく)。非常に低い調整しか必要としないユースケースの場合は、代わりにAnyメッセージの使用を検討してください。

未検証の拡張フィールド番号割り当て戦略は、1から524,999,999の範囲に限定されます。フィールド番号525,000,000以上は、拡張宣言でのみ使用できます。

拡張タイプの指定

拡張機能は、oneofmapを除くすべてのフィールド型を持つことができます。

ネストされた拡張機能 (非推奨)

別のメッセージのスコープ内で拡張機能を宣言できます。

import "common/user_profile.proto";

package puppies;

message Photo {
  extend common.UserProfile {
    optional int32 likes_count = 111;
  }
  ...
}

この場合、この拡張機能にアクセスするための C++ コードは次のようになります。

UserProfile user_profile;
user_profile.SetExtension(puppies::Photo::likes_count, 42);

言い換えれば、唯一の効果は、likes_countpuppies.Photoのスコープ内で定義されることです。

これは一般的な混乱の原因です。メッセージ型の内部にネストされたextendブロックを宣言しても、外部型と拡張型の間に関係があることを意味しません。特に、先の例は、PhotoUserProfileのサブクラスであることを意味しません。それは単にシンボルlikes_countPhotoのスコープ内で宣言されていることを意味します。それは単なる静的メンバーです。

一般的なパターンは、拡張機能のフィールドタイプのスコープ内で拡張機能を定義することです。たとえば、media.UserContentに対するpuppies.Photo型の拡張機能で、拡張機能がPhotoの一部として定義されている例は次のとおりです。

import "media/user_content.proto";

package puppies;

message Photo {
  extend media.UserContent {
    optional Photo puppy_photo = 127;
  }
  ...
}

ただし、メッセージ型を持つ拡張機能がその型内部で定義されるという要件はありません。標準の定義パターンを使用することもできます。

import "media/user_content.proto";

package puppies;

message Photo {
  ...
}

// This can even be in a different file.
extend media.UserContent {
  optional Photo puppy_photo = 127;
}

混乱を避けるため、この標準(ファイルレベル)構文が推奨されます。ネストされた構文は、拡張機能にすでに精通していないユーザーによって、しばしばサブクラス化と誤解されます。

Any

Anyメッセージタイプを使用すると、メッセージの.proto定義を持たずに、メッセージを埋め込み型として使用できます。Anyには、任意のシリアライズされたメッセージがbytesとして含まれ、そのメッセージの型をグローバルに一意に識別し解決する URL が付随します。Any型を使用するには、google/protobuf/any.protoインポートする必要があります。

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

指定されたメッセージタイプのデフォルトの型 URL はtype.googleapis.com/_packagename_._messagename_です。

異なる言語の実装では、Any値を型安全な方法でパックおよびアンパックするためのランタイムライブラリヘルパーがサポートされます。たとえば、Java ではAny型に特別なpack()およびunpack()アクセサーがあり、C++ ではPackFrom()およびUnpackTo()メソッドがあります。

// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);

// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const google::protobuf::Any& detail : status.details()) {
  if (detail.Is<NetworkErrorDetails>()) {
    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}

含まれるメッセージを少数のタイプに制限し、リストに新しいタイプを追加する前に許可を必要とする場合は、Anyメッセージタイプではなく、拡張機能拡張宣言の使用を検討してください。

Oneof

多くのオプションフィールドを持つメッセージがあり、同時に設定されるフィールドが最大で1つである場合、oneof 機能を使用することでこの動作を強制し、メモリを節約できます。

Oneof フィールドはオプションフィールドと似ていますが、Oneof 内のすべてのフィールドがメモリを共有し、同時に設定できるフィールドは最大で1つという点が異なります。Oneof の任意のメンバーを設定すると、他のすべてのメンバーは自動的にクリアされます。選択した言語に応じて、特殊なcase()またはWhichOneof()メソッドを使用して、Oneof 内のどの値が設定されているか(もしあれば)を確認できます。

複数の値が設定されている場合、プロト内の順序で決定される最後に設定された値が以前のすべての値を上書きします

oneof フィールドのフィールド番号は、囲むメッセージ内で一意である必要があります。

Oneof の使用

.protoで oneof を定義するには、oneofキーワードの後に oneof 名(この場合はtest_oneof)を使用します。

message SampleMessage {
  oneof test_oneof {
     string name = 4;
     SubMessage sub_message = 9;
  }
}

次に、oneof フィールドを oneof 定義に追加します。mapフィールドを除く任意の型のフィールドを追加できますが、requiredoptional、またはrepeatedキーワードは使用できません。繰り返しフィールドを oneof に追加する必要がある場合は、繰り返しフィールドを含むメッセージを使用できます。

生成されたコードでは、oneof フィールドは通常のoptionalフィールドと同じゲッターとセッターを持ちます。また、oneof 内のどの値(もしあれば)が設定されているかを確認するための特別なメソッドも利用できます。選択した言語の oneof API の詳細については、関連するAPI リファレンスを参照してください。

Oneof の機能

  • oneof フィールドを設定すると、oneof の他のすべてのメンバーが自動的にクリアされます。したがって、複数の oneof フィールドを設定した場合、値が残るのは最後に設定したフィールドのみです。

    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    // Calling mutable_sub_message() will clear the name field and will set
    // sub_message to a new instance of SubMessage with none of its fields set.
    message.mutable_sub_message();
    CHECK(!message.has_name());
    
  • パーサーがワイヤー上で同じ oneof の複数のメンバーを検出した場合、解析されたメッセージでは最後に検出されたメンバーのみが使用されます。ワイヤー上のデータを解析する際、バイトの先頭から開始し、次の値を評価して、以下の解析ルールを適用します。

    • まず、同じ oneof 内の異なるフィールドが現在設定されているかどうかを確認し、設定されている場合はそれをクリアします。

    • 次に、フィールドが oneof に含まれていなかったかのように内容を適用します。

      • プリミティブは、すでに設定されている値を上書きします。
      • メッセージは、すでに設定されている任意の値にマージされます。
  • oneof では拡張機能はサポートされていません。

  • oneof はrepeatedにできません。

  • リフレクション API は oneof フィールドで機能します。

  • oneof フィールドをデフォルト値(int32 oneof フィールドを0に設定するなど)に設定した場合、その oneof フィールドの「ケース」が設定され、値はワイヤーにシリアライズされます。

  • C++ を使用している場合、コードがメモリクラッシュを引き起こさないようにしてください。以下のサンプルコードは、set_name()メソッドの呼び出しによってsub_messageがすでに削除されているため、クラッシュします。

    SampleMessage message;
    SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes here
    
  • C++ の場合、oneof を持つ2つのメッセージをSwap()すると、各メッセージはもう一方の oneof ケースを持つことになります。以下の例では、msg1sub_messageを持ち、msg2nameを持ちます。

    SampleMessage msg1;
    msg1.set_name("name");
    SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK(msg2.has_name());
    

後方互換性の問題

oneof フィールドを追加または削除する際には注意してください。oneof の値をチェックしてNone/NOT_SETが返された場合、それは oneof が設定されていないか、または oneof の別のバージョン内のフィールドに設定されていることを意味する可能性があります。ワイヤー上の未知のフィールドが oneof のメンバーであるかどうかを知る方法がないため、その違いを区別する方法はありません。

タグ再利用の問題

  • オプションフィールドを oneof の内外に移動する: メッセージがシリアライズおよび解析された後、情報の一部が失われる可能性があります(一部のフィールドがクリアされます)。ただし、単一のフィールドを新しい oneof に安全に移動でき、一度に1つしか設定されないことがわかっている場合は、複数のフィールドを移動できる可能性があります。詳細については、メッセージタイプの更新を参照してください。
  • oneof フィールドを削除して再追加する: これにより、メッセージがシリアライズおよび解析された後、現在設定されている oneof フィールドがクリアされる可能性があります。
  • oneof を分割またはマージする: これはoptionalフィールドの移動と同様の問題があります。

マップ

データ定義の一部として連想マップを作成したい場合、プロトコルバッファは便利なショートカット構文を提供します。

map<key_type, value_type> map_field = N;

...ここでkey_typeは任意の整数型または文字列型(つまり、浮動小数点型とbytesを除くすべてのスカラー型)にすることができます。key_typeには enum も proto メッセージも無効であることに注意してください。value_typeは別のマップを除く任意の型にすることができます。

たとえば、各Projectメッセージが文字列キーと関連付けられるプロジェクトのマップを作成したい場合、次のように定義できます。

map<string, Project> projects = 3;

マップの機能

  • マップでは拡張機能はサポートされていません。
  • マップはrepeatedoptional、またはrequiredにすることはできません。
  • マップ値のワイヤー形式の順序付けとマップのイテレーション順序は未定義であるため、マップ項目が特定の順序であることに依存することはできません。
  • .protoのテキスト形式を生成する場合、マップはキーでソートされます。数値キーは数値的にソートされます。
  • ワイヤーから解析する場合やマージする場合、重複するマップキーがある場合は、最後に検出されたキーが使用されます。テキスト形式からマップを解析する場合、重複するキーがあると解析が失敗する可能性があります。
  • マップフィールドにキーは提供するが値を提供しない場合、フィールドがシリアライズされる際の動作は言語に依存します。C++、Java、Kotlin、Python では型のデフォルト値がシリアライズされますが、他の言語では何もシリアライズされません。
  • マップfooと同じスコープにFooEntryシンボルは存在できません。これは、FooEntryがマップの実装によってすでに使用されているためです。

生成されたマップ API は現在、すべてのサポートされている言語で利用可能です。選択した言語のマップ API の詳細については、関連するAPI リファレンスを参照してください。

後方互換性

マップ構文はワイヤー上で以下と同等であるため、マップをサポートしないプロトコルバッファの実装でもデータは処理できます。

message MapFieldEntry {
  optional key_type key = 1;
  optional value_type value = 2;
}

repeated MapFieldEntry map_field = N;

マップをサポートするすべてのプロトコルバッファの実装は、以前の定義によって受け入れられるデータを生成および受け入れる必要があります。

パッケージ

プロトコルメッセージタイプ間の名前の衝突を防ぐために、.protoファイルにオプションのpackage指定子を追加できます。

package foo.bar;
message Open { ... }

その後、メッセージタイプのフィールドを定義する際にパッケージ指定子を使用できます。

message Foo {
  ...
  optional foo.bar.Open open = 1;
  ...
}

パッケージ指定子が生成されたコードに影響を与える方法は、選択した言語によって異なります。

  • C++では、生成されたクラスは C++ 名前空間内にラップされます。たとえば、Openfoo::bar名前空間に存在します。
  • JavaKotlinでは、.protoファイルでoption java_packageを明示的に指定しない限り、パッケージが Java パッケージとして使用されます。
  • Pythonでは、Python モジュールはファイルシステム内の場所に基づいて編成されるため、packageディレクティブは無視されます。
  • Goでは、packageディレクティブは無視され、生成された.pb.goファイルは対応するgo_proto_library Bazel ルールの名前が付けられたパッケージに存在します。オープンソースプロジェクトの場合、go_packageオプションを提供するか、Bazelの-Mフラグを設定するかのいずれか必須とします。
  • Rubyでは、生成されたクラスはネストされた Ruby 名前空間内にラップされ、必要な Ruby の大文字化スタイルに変換されます(最初の文字が大文字。最初の文字が文字でない場合はPB_が前置されます)。たとえば、OpenFoo::Bar名前空間に存在します。
  • PHPでは、.protoファイルでoption php_namespaceを明示的に指定しない限り、パッケージは PascalCase に変換された後に名前空間として使用されます。たとえば、OpenFoo\Bar名前空間に存在します。
  • C#では、.protoファイルでoption csharp_namespaceを明示的に指定しない限り、パッケージは PascalCase に変換された後に名前空間として使用されます。たとえば、OpenFoo.Bar名前空間に存在します。

packageディレクティブが Python のように生成されたコードに直接影響を与えない場合でも、.protoファイルにパッケージを指定することを強くお勧めします。そうしないと、ディスクリプタで名前の衝突が発生したり、他の言語で proto が移植不可能になったりする可能性があるためです。

パッケージと名前解決

プロトコルバッファ言語における型名解決は C++ と同様に機能します。まず最も内側のスコープが検索され、次にその外側のスコープが検索されます。各パッケージは親パッケージの「内側」にあると見なされます。先頭の「.」(例: .foo.bar.Baz)は、代わりに最も外側のスコープから開始することを意味します。

プロトコルバッファコンパイラは、インポートされた.protoファイルを解析することで、すべての型名を解決します。各言語のコードジェネレータは、スコープ規則が異なる場合でも、その言語で各型を参照する方法を知っています。

サービスの定義

メッセージ型を RPC(リモートプロシージャコール)システムで使用したい場合、.protoファイルで RPC サービスインターフェースを定義すると、プロトコルバッファコンパイラが選択した言語でサービスインターフェースコードとスタブを生成します。たとえば、SearchRequestを受け取り、SearchResponseを返すメソッドを持つ RPC サービスを定義したい場合、.protoファイルで次のように定義できます。

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

デフォルトでは、プロトコルコンパイラはSearchServiceと呼ばれる抽象インターフェースと、対応する「スタブ」実装を生成します。スタブはすべての呼び出しをRpcChannelに転送します。これは、自身の RPC システムの観点から自分で定義する必要がある抽象インターフェースです。たとえば、メッセージをシリアライズして HTTP 経由でサーバーに送信するRpcChannelを実装することもできます。つまり、生成されたスタブは、特定の RPC 実装に縛られることなく、プロトコルバッファベースの RPC 呼び出しを行うための型安全なインターフェースを提供します。したがって、C++ では次のようなコードになります。

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

void DoSearch() {
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = new MyRpcChannel("somehost.example.com:1234");
  controller = new MyRpcController;

  // The protocol compiler generates the SearchService class based on the
  // definition given earlier.
  service = new SearchService::Stub(channel);

  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, &request, &response,
                  protobuf::NewCallback(&Done));
}

void Done() {
  delete service;
  delete channel;
  delete controller;
}

すべてのサービス クラスは、コンパイル時にメソッド名やその入力および出力型を知らなくても、特定のメソッドを呼び出す方法を提供するServiceインターフェースも実装しています。サーバー側では、これを使用してサービスを登録できる RPC サーバーを実装できます。

using google::protobuf;

class ExampleSearchService : public SearchService {
 public:
  void Search(protobuf::RpcController* controller,
              const SearchRequest* request,
              SearchResponse* response,
              protobuf::Closure* done) {
    if (request->query() == "google") {
      response->add_result()->set_url("http://www.google.com");
    } else if (request->query() == "protocol buffers") {
      response->add_result()->set_url("http://protobuf.googlecode.com");
    }
    done->Run();
  }
};

int main() {
  // You provide class MyRpcServer.  It does not have to implement any
  // particular interface; this is just an example.
  MyRpcServer server;

  protobuf::Service* service = new ExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return 0;
}

既存の独自の RPC システムを組み込みたくない場合は、Google で開発された言語およびプラットフォームに依存しないオープンソースの RPC システムであるgRPCを使用できます。gRPC はプロトコルバッファと特にうまく連携し、特別なプロトコルバッファコンパイラプラグインを使用して、.protoファイルから関連する RPC コードを直接生成できます。ただし、proto2 と proto3 で生成されたクライアントとサーバー間には互換性の問題が発生する可能性があるため、gRPC サービスの定義には proto3 または 2023 エディションを使用することをお勧めします。proto3 構文の詳細についてはProto3 言語ガイドを、2023 エディションについては2023 エディション言語ガイドを参照してください。

gRPC に加えて、Protocol Buffers 用の RPC 実装を開発するための進行中のサードパーティプロジェクトも多数あります。私たちが知っているプロジェクトへのリンクのリストについては、サードパーティアドオンのwikiページを参照してください。

JSON マッピング

標準の protobuf バイナリワイヤー形式は、protobuf を使用する2つのシステム間の通信で推奨されるシリアライズ形式です。protobuf ワイヤー形式ではなく JSON を使用するシステムとの通信には、Protobuf はJSONでの正規エンコーディングをサポートしています。

オプション

.protoファイル内の個々の宣言には、いくつかのオプションを付加することができます。オプションは宣言の全体的な意味を変更しませんが、特定のコンテキストでの処理方法に影響を与える可能性があります。利用可能なオプションの完全なリストは、/google/protobuf/descriptor.protoで定義されています。

一部のオプションはファイルレベルのオプションであり、メッセージ、enum、またはサービス定義の内部ではなく、トップレベルスコープに記述する必要があります。一部のオプションはメッセージレベルのオプションであり、メッセージ定義の内部に記述する必要があります。一部のオプションはフィールドレベルのオプションであり、フィールド定義の内部に記述する必要があります。オプションは enum 型、enum 値、oneof フィールド、サービス型、およびサービスメソッドにも記述できますが、現在、これらのいずれにも有用なオプションは存在しません。

よく使われるオプションをいくつか紹介します。

  • java_package (ファイルオプション): 生成される Java/Kotlin クラスに使用するパッケージです。.protoファイルで明示的なjava_packageオプションが指定されていない場合、デフォルトで proto パッケージ(.protoファイルの「package」キーワードを使用して指定)が使用されます。ただし、proto パッケージは通常、逆引きドメイン名で始まるとは期待されていないため、良い Java パッケージにはなりません。Java または Kotlin コードを生成しない場合、このオプションは効果がありません。

    option java_package = "com.example.foo";
    
  • java_outer_classname (ファイルオプション): 生成したいラッパー Java クラスのクラス名(したがってファイル名)です。.protoファイルで明示的なjava_outer_classnameが指定されていない場合、クラス名は.protoファイル名をキャメルケースに変換して構築されます(例: foo_bar.protoFooBar.javaになります)。java_multiple_filesオプションが無効になっている場合、.protoファイル用に生成された他のすべてのクラス/enum などは、この外側のラッパー Java クラスの内部にネストされたクラス/enum などとして生成されます。Java コードを生成しない場合、このオプションは効果がありません。

    option java_outer_classname = "Ponycopter";
    
  • java_multiple_files (ファイルオプション): false の場合、この.protoファイルに対して単一の.javaファイルのみが生成され、トップレベルのメッセージ、サービス、および列挙型用に生成されたすべての Java クラス/enum などは、外部クラス(java_outer_classnameを参照)内にネストされます。true の場合、トップレベルのメッセージ、サービス、および列挙型用に生成された各 Java クラス/enum などに対して個別の.javaファイルが生成され、この.protoファイル用に生成されたラッパー Java クラスにはネストされたクラス/enum などは含まれません。これはブール値オプションで、デフォルトはfalseです。Java コードを生成しない場合、このオプションは効果がありません。

    option java_multiple_files = true;
    
  • optimize_for (ファイルオプション): SPEEDCODE_SIZE、またはLITE_RUNTIMEに設定できます。これは、C++ および Java のコードジェネレータ(およびおそらくサードパーティのジェネレータ)に次の方法で影響します。

    • SPEED (デフォルト): プロトコルバッファコンパイラは、メッセージ型をシリアライズ、解析、およびその他の一般的な操作を実行するためのコードを生成します。このコードは高度に最適化されています。
    • CODE_SIZE: プロトコルバッファコンパイラは最小限のクラスを生成し、シリアライズ、解析、およびその他のさまざまな操作を実装するために、共有されたリフレクションベースのコードに依存します。したがって、生成されるコードはSPEEDよりもはるかに小さくなりますが、操作は遅くなります。クラスはSPEEDモードとまったく同じパブリック API を引き続き実装します。このモードは、非常に多数の.protoファイルを含み、それらすべてが非常に高速である必要がないアプリで最も有用です。
    • LITE_RUNTIME: プロトコルバッファコンパイラは、「lite」ランタイムライブラリ(libprotobufの代わりにlibprotobuf-lite)のみに依存するクラスを生成します。lite ランタイムはフルライブラリよりもはるかに小さく(約1桁小さい)、ディスクリプタやリフレクションなどの特定の機能を省略します。これは、携帯電話のような制約されたプラットフォームで実行されるアプリに特に有用です。コンパイラはSPEEDモードと同様に、すべてのメソッドの高速な実装を引き続き生成します。生成されるクラスは、各言語でMessageLiteインターフェースのみを実装します。これは、完全なMessageインターフェースのメソッドのサブセットのみを提供します。
    option optimize_for = CODE_SIZE;
    
  • cc_generic_services, java_generic_services, py_generic_services (ファイルオプション): 汎用サービスは非推奨です。プロトコルバッファコンパイラが C++、Java、Python でのサービス定義に基づいて抽象サービスコードを生成すべきかどうかをそれぞれ示します。レガシーな理由から、これらはデフォルトでtrueです。しかし、バージョン2.3.0 (2010年1月) 以降では、RPC 実装が「抽象」サービスに依存するのではなく、各システムにより特化したコードを生成するためのコード生成プラグインを提供することが好ましいとされています。

    // This file relies on plugins to generate service code.
    option cc_generic_services = false;
    option java_generic_services = false;
    option py_generic_services = false;
    
  • cc_enable_arenas (ファイルオプション): C++ で生成されたコードのアリーナ割り当てを有効にします。

  • objc_class_prefix (ファイルオプション): この .proto から生成されるすべての Objective-C クラスおよび enum の前に付加される Objective-C クラス接頭辞を設定します。デフォルト値はありません。Apple が推奨するように、3~5文字の大文字の接頭辞を使用する必要があります。2文字の接頭辞はすべて Apple によって予約されていることに注意してください。

  • message_set_wire_format (メッセージオプション): trueに設定すると、メッセージは Google 内部でMessageSetと呼ばれる古い形式と互換性のある別のバイナリ形式を使用します。Google 以外のユーザーがこのオプションを使用する必要はおそらくありません。メッセージは次のように正確に宣言する必要があります。

    message Foo {
      option message_set_wire_format = true;
      extensions 4 to max;
    }
    
  • packed (フィールドオプション): 基本的な数値型の繰り返しフィールドに true を設定すると、よりコンパクトなエンコーディングが使用されます。このオプションを使用しない唯一の理由は、バージョン 2.3.0 以前のパーサーとの互換性が必要な場合です。これらの古いパーサーは、予期しない `packed` データを無視していました。そのため、既存のフィールドを `packed` 形式に変更すると、ワイヤー互換性が損なわれていました。バージョン 2.3.0 以降では、`packable` なフィールドのパーサーは常に両方の形式を受け入れるため、この変更は安全です。ただし、古い protobuf バージョンを使用している古いプログラムを扱う場合は注意してください。

    repeated int32 samples = 4 [packed = true];
    
  • deprecated (フィールドオプション): true に設定されている場合、そのフィールドは非推奨であり、新しいコードでは使用すべきではないことを示します。ほとんどの言語では、これによる実際的な効果はありません。Java では、これは @Deprecated アノテーションになります。C++ では、`deprecated` フィールドが使用されるたびに clang-tidy が警告を生成します。将来的には、他の言語固有のコードジェネレーターがフィールドのアクセサーに非推奨アノテーションを生成し、その結果、そのフィールドを使用しようとするコードをコンパイルする際に警告が発行されるようになる可能性があります。そのフィールドが誰にも使用されておらず、新しいユーザーがそれを使用するのを防ぎたい場合は、フィールド宣言を予約済みステートメントに置き換えることを検討してください。

    optional int32 old_field = 6 [deprecated=true];
    

Enum 値のオプション

Enum 値オプションがサポートされています。値がもう使用されるべきではないことを示すために、deprecated オプションを使用できます。拡張機能を使用してカスタムオプションを作成することもできます。

以下の例は、これらのオプションを追加するための構文を示しています

import "google/protobuf/descriptor.proto";

extend google.protobuf.EnumValueOptions {
  optional string string_name = 123456789;
}

enum Data {
  DATA_UNSPECIFIED = 0;
  DATA_SEARCH = 1 [deprecated = true];
  DATA_DISPLAY = 2 [
    (string_name) = "display_value"
  ];
}

string_name オプションを読み取る C++ コードは、次のようになります

const absl::string_view foo = proto2::GetEnumDescriptor<Data>()
    ->FindValueByName("DATA_DISPLAY")->options().GetExtension(string_name);

Enum 値やフィールドにカスタムオプションを適用する方法については、「カスタムオプション」を参照してください。

カスタムオプション

Protocol Buffers は、独自のオプションを定義して使用することも可能にします。これはほとんどの人には必要のない高度な機能であることに注意してください。オプションは google/protobuf/descriptor.proto で定義されたメッセージ (FileOptionsFieldOptions など) によって定義されているため、独自のオプションを定義するには、それらのメッセージを拡張するだけで済みます。例:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}

message MyMessage {
  option (my_option) = "Hello world!";
}

ここでは、MessageOptions を拡張して新しいメッセージレベルのオプションを定義しました。オプションを使用する際には、それが拡張機能であることを示すために、オプション名を括弧で囲む必要があります。これで、C++ で my_option の値を次のように読み取ることができます。

string value = MyMessage::descriptor()->options().GetExtension(my_option);

ここで、MyMessage::descriptor()->options()MyMessageMessageOptions プロトコルメッセージを返します。そこからカスタムオプションを読み取るのは、他の拡張機能を読み取るのと全く同じです。

同様に、Java では次のように記述します

String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
  .getExtension(MyProtoFile.myOption);

Python では次のようになります

value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
  .Extensions[my_proto_file_pb2.my_option]

カスタムオプションは、Protocol Buffers 言語のあらゆる種類の構成要素に対して定義できます。あらゆる種類のオプションを使用する例を次に示します

import "google/protobuf/descriptor.proto";

extend google.protobuf.FileOptions {
  optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
  optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
  optional float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
  optional int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
  optional bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
  optional uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
  optional MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
  optional MyMessage my_method_option = 50007;
}

option (my_file_option) = "Hello world!";

message MyMessage {
  option (my_message_option) = 1234;

  optional int32 foo = 1 [(my_field_option) = 4.5];
  optional string bar = 2;
  oneof qux {
    option (my_oneof_option) = 42;

    string quux = 3;
  }
}

enum MyEnum {
  option (my_enum_option) = true;

  FOO = 1 [(my_enum_value_option) = 321];
  BAR = 2;
}

message RequestType {}
message ResponseType {}

service MyService {
  option (my_service_option) = FOO;

  rpc MyMethod(RequestType) returns(ResponseType) {
    // Note:  my_method_option has type MyMessage.  We can set each field
    //   within it using a separate "option" line.
    option (my_method_option).foo = 567;
    option (my_method_option).bar = "Some string";
  }
}

定義されたパッケージ以外のパッケージでカスタムオプションを使用したい場合は、型名と同様に、オプション名にパッケージ名をプレフィックスとして付加する必要があることに注意してください。例:

// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
  option (foo.my_option) = "Hello world!";
}

最後に、カスタムオプションは拡張機能であるため、他のフィールドや拡張機能と同様にフィールド番号を割り当てる必要があります。以前の例では、50000-99999 の範囲のフィールド番号を使用しました。この範囲は個々の組織内での内部使用のために予約されているため、社内アプリケーションではこの範囲の番号を自由に使用できます。ただし、公開アプリケーションでカスタムオプションを使用する場合は、フィールド番号がグローバルに一意であることを確認することが重要です。グローバルに一意のフィールド番号を取得するには、protobuf グローバル拡張レジストリにエントリを追加するリクエストを送信してください。通常、必要な拡張番号は1つだけです。サブメッセージに配置することで、1つの拡張番号だけで複数のオプションを宣言できます。

message FooOptions {
  optional int32 opt1 = 1;
  optional string opt2 = 2;
}

extend google.protobuf.FieldOptions {
  optional FooOptions foo_options = 1234;
}

// usage:
message Bar {
  optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
  // alternative aggregate syntax (uses TextFormat):
  optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}

また、各オプションタイプ (ファイルレベル、メッセージレベル、フィールドレベルなど) は独自の番号空間を持っているため、たとえば FieldOptions と MessageOptions の拡張機能を同じ番号で宣言できることに注意してください。

オプションの保持

オプションには保持 (retention)という概念があり、生成されたコードにオプションが保持されるかどうかを制御します。オプションはデフォルトでランタイム保持 (runtime retention)を持ちます。これは、生成されたコードに保持され、生成された記述子プール内でランタイム時に可視であることを意味します。ただし、`retention = RETENTION_SOURCE` を設定することで、オプション (またはオプション内のフィールド) がランタイム時に保持されないように指定できます。これはソース保持 (source retention)と呼ばれます。

オプションの保持は、ほとんどのユーザーが心配する必要のない高度な機能ですが、バイナリでそれらを保持するコードサイズのコストを支払うことなく、特定のオプションを使用したい場合に役立ちます。ソース保持を持つオプションは、protoc および protoc プラグインには引き続き可視であるため、コードジェネレーターはそれらを使用して動作をカスタマイズできます。

保持はオプションに直接設定できます。例:

extend google.protobuf.FileOptions {
  optional int32 source_retention_option = 1234
      [retention = RETENTION_SOURCE];
}

通常のフィールドにも設定でき、その場合はそのフィールドがオプション内に現れたときにのみ有効になります。

message OptionsMessage {
  optional int32 source_retention_field = 1 [retention = RETENTION_SOURCE];
}

`retention = RETENTION_RUNTIME` を設定することもできますが、これはデフォルトの動作であるため効果はありません。メッセージフィールドが RETENTION_SOURCE とマークされると、その内容全体が破棄されます。その内部のフィールドは、RETENTION_RUNTIME を設定しようとしてもそれを上書きすることはできません。

オプションのターゲット

フィールドには `targets` オプションがあり、オプションとして使用される場合に、そのフィールドが適用されるエンティティのタイプを制御します。たとえば、フィールドが `targets = TARGET_TYPE_MESSAGE` を持つ場合、そのフィールドは enum (またはその他の非メッセージエンティティ) のカスタムオプションとして設定することはできません。Protoc はこれを強制し、ターゲット制約に違反がある場合はエラーを発生させます。

一見すると、すべてのカスタムオプションは特定のエンティティのオプションメッセージの拡張であり、すでにオプションをその1つのエンティティに制約していることを考えると、この機能は不要に見えるかもしれません。しかし、複数のエンティティタイプに適用される共有オプションメッセージがあり、そのメッセージ内の個々のフィールドの使用を制御したい場合に、オプションターゲットは役立ちます。例:

message MyOptions {
  optional string file_only_option = 1 [targets = TARGET_TYPE_FILE];
  optional int32 message_and_enum_option = 2 [targets = TARGET_TYPE_MESSAGE,
                                              targets = TARGET_TYPE_ENUM];
}

extend google.protobuf.FileOptions {
  optional MyOptions file_options = 50000;
}

extend google.protobuf.MessageOptions {
  optional MyOptions message_options = 50000;
}

extend google.protobuf.EnumOptions {
  optional MyOptions enum_options = 50000;
}

// OK: this field is allowed on file options
option (file_options).file_only_option = "abc";

message MyMessage {
  // OK: this field is allowed on both message and enum options
  option (message_options).message_and_enum_option = 42;
}

enum MyEnum {
  MY_ENUM_UNSPECIFIED = 0;
  // Error: file_only_option cannot be set on an enum.
  option (enum_options).file_only_option = "xyz";
}

クラスの生成

.proto ファイルで定義されたメッセージタイプを操作するために必要な Java、Kotlin、Python、C++、Go、Ruby、Objective-C、または C# のコードを生成するには、.proto ファイルに対してプロトコルバッファコンパイラ protoc を実行する必要があります。コンパイラをインストールしていない場合は、パッケージをダウンロードし、README の指示に従ってください。Go の場合は、コンパイラ用の特別なコードジェネレータープラグインもインストールする必要があります。これとインストール手順は、GitHub の golang/protobuf リポジトリで見つけることができます。

プロトコルコンパイラは次のように起動されます。

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH は、import ディレクティブを解決する際に .proto ファイルを探すディレクトリを指定します。省略された場合、現在のディレクトリが使用されます。複数のインポートディレクトリは、--proto_path オプションを複数回渡すことで指定できます。それらは順序通りに検索されます。-I=_IMPORT_PATH_--proto_path の短縮形として使用できます。

注意: proto_path を基準としたファイルパスは、所与のバイナリ内でグローバルに一意である必要があります。たとえば、proto/lib1/data.protoproto/lib2/data.proto がある場合、-I=proto/lib1 -I=proto/lib2 と一緒にこれらの2つのファイルを使用することはできません。なぜなら、import "data.proto" がどのファイルを意味するかが曖昧になるからです。代わりに -Iproto/ を使用し、グローバル名が lib1/data.protolib2/data.proto となるようにしてください。

ライブラリを公開しており、他のユーザーがあなたのメッセージを直接使用する可能性がある場合は、ファイル名の衝突を避けるために、使用が想定されるパスに一意のライブラリ名を含めるべきです。1つのプロジェクトに複数のディレクトリがある場合は、プロジェクトのトップレベルディレクトリに1つの -I を設定することを推奨します。

  • 1つ以上の出力ディレクティブを指定できます。

    さらに便利な機能として、DST_DIR.zip または .jar で終わる場合、コンパイラは出力を指定された名前の単一の ZIP 形式アーカイブファイルに書き込みます。.jar の出力には、Java JAR 仕様で要求されるマニフェストファイルも付与されます。出力アーカイブがすでに存在する場合、上書きされることに注意してください。

  • 1つ以上の .proto ファイルを入力として提供する必要があります。複数の .proto ファイルを一度に指定できます。ファイルはカレントディレクトリからの相対名ですが、コンパイラがその正規名を決定できるように、各ファイルは IMPORT_PATH のいずれかに存在する必要があります。

ファイルの場所

.proto ファイルを他の言語ソースと同じディレクトリに置かないことを推奨します。プロジェクトのルートパッケージの下に、.proto ファイル用のサブパッケージ proto を作成することを検討してください。

場所は言語に依存しないべき

Java コードを扱う際、関連する .proto ファイルを Java ソースと同じディレクトリに置くと便利です。しかし、非 Java コードが同じプロトを使用する場合、パスプレフィックスは意味をなさなくなります。したがって、一般的には、プロトを //myteam/mypackage のような関連する言語非依存のディレクトリに配置してください。

この規則の例外は、テストなど、プロトが Java コンテキストでのみ使用されることが明確な場合です。

サポートされているプラットフォーム

詳細については