言語ガイド (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 つのフィールド (名前/値ペア) を指定します。これは、このタイプのメッセージに含めるデータの一部ごとに 1 つずつです。各フィールドには、名前と型があります。

フィールド型の指定

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

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

メッセージ定義の各フィールドには、1 から 536,870,911 までの数値を次の制限付きで付与する必要があります。

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

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

フィールド番号は再利用しないでください。新しいフィールド定義で再利用するために、予約済みリストからフィールド番号を削除しないでください。「フィールド番号の再利用の悪影響」を参照してください。

最も頻繁に設定されるフィールドには、フィールド番号 1 から 15 を使用する必要があります。フィールド番号の値が小さいほど、ワイヤ形式でのスペースが少なくなります。たとえば、1 から 15 の範囲のフィールド番号は、エンコードに 1 バイトかかります。16 から 2047 の範囲のフィールド番号は、2 バイトかかります。詳細については、「プロトコル バッファのエンコード」を参照してください。

フィールド番号の再利用の悪影響

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

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

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

  • デバッグに時間を費やす開発時間
  • 解析/マージエラー (最良のシナリオ)
  • PII/SPII のリーク
  • データの破損

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

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

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

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

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

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

  • 単数:

    proto2 では、単数フィールドには 2 つのタイプがあります。

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

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

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

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

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

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

新しい repeated フィールドには Packed エンコードを使用する

歴史的な理由から、スカラー数値型 (たとえば、int32int64enum) の repeated フィールドは、可能な限り効率的にエンコードされていません。新しいコードでは、より効率的なエンコードを取得するために、特別なオプション [packed = true] を使用する必要があります。例:

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

packed エンコードの詳細については、「プロトコル バッファのエンコード」を参照してください。

Required は強く非推奨

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

整形式のメッセージ

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 {
 ...
}

メッセージの組み合わせは肥大化につながります 複数のメッセージ型 (メッセージ、enum、サービスなど) を単一の .proto ファイルで定義できますが、依存関係が異なる多数のメッセージが単一のファイルで定義されている場合、依存関係の肥大化につながる可能性もあります。.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 to 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可変長エンコードを使用します。符号付き int 値。これらは、負の数を通常の int32 よりも効率的にエンコードします。
sint64可変長エンコードを使用します。符号付き int 値。これらは、負の数を通常の 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 からの対応する型を使用して、Java/Kotlin コードベースが混在する場合の互換性を確保します。

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

[3] すべての場合において、フィールドに値を設定すると、型チェックが実行され、有効であることが確認されます。

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

[5] Proto2 は通常、文字列フィールドの UTF-8 有効性をチェックしません。ただし、言語によって動作が異なり、無効な UTF-8 データは文字列フィールドに格納しないでください。

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

これらの型をメッセージをシリアライズするときにどのようにエンコードするかについて詳しくは、「プロトコル バッファのエンコード」を参照してください。

フィールドのデフォルト値

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

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

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

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

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

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

optional int32 results_per_page = 3 [default = 10];

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

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

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

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

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

列挙型

メッセージ型を定義するときに、そのフィールドの 1 つが事前定義された値のリストの 1 つだけを持つようにしたい場合があります。たとえば、各 SearchRequestcorpus フィールドを追加するとします。corpus は、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 ファイルの任意のメッセージ定義で再利用できます。また、_MessageType_._EnumType_ 構文を使用して、あるメッセージで宣言された enum 型を別のメッセージのフィールドの型として使用することもできます。

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

enum 値を削除すると、永続化された proto の破壊的な変更になります。値を削除する代わりに、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 エントリを完全に削除するか、コメントアウトして更新する場合、将来のユーザーは、型を独自に更新するときに数値valueを再利用できます。後で同じ .proto の古いインスタンスをロードすると、データの破損、プライバシーのバグなど、深刻な問題が発生する可能性があります。これを確実に回避する方法の 1 つは、削除されたエントリの数値value (および JSON シリアライゼーションの問題を引き起こす可能性のある名前) を reserved に指定することです。プロトコル バッファ コンパイラは、将来のユーザーがこれらの識別子を使用しようとすると警告を発します。max キーワードを使用して、予約済みの数値value範囲が可能な最大値までになるように指定できます。

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

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

他のメッセージ型の使用

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

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 ステートメントを追加します。

import "myproject/other_protos.proto";

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

public import 機能は、Java、Kotlin、TypeScript、JavaScript、GCL、および protobuf 静的リフレクションを使用する C++ ターゲットでは使用できないことに注意してください。

import public 依存関係は、import public ステートメントを含む proto をインポートするすべてのコードによって推移的に依存される可能性があります。例:

// 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 は互いに互換性がありますが、他の整数型とは互換性がありません
  • stringbytes は、バイトが有効な UTF-8 であれば互換性があります。
  • 埋め込みメッセージは、バイトにメッセージのエンコードされたインスタンスが含まれている場合、bytes と互換性があります。
  • fixed32sfixed32 と、fixed64sfixed64 と互換性があります。
  • stringbytes、およびメッセージフィールドの場合、singular は repeated と互換性があります。repeated フィールドのシリアライズされたデータが入力として与えられた場合、このフィールドが singular であることを期待するクライアントは、それがプリミティブ型フィールドの場合は最後の入力値を取得し、メッセージ型フィールドの場合はすべての入力要素をマージします。これは、bool や enum を含む数値型には一般に安全ではないことに注意してください。数値型の repeated フィールドは、packed 形式でシリアライズされる場合があります。これは、singular フィールドが期待される場合には正しく解析されません。
  • デフォルト値を変更することは一般に問題ありません。ただし、デフォルト値はワイヤ経由で送信されないことを覚えておく必要があります。したがって、プログラムが特定のフィールドが設定されていないメッセージを受信した場合、プログラムはプロトコルのそのプログラムのバージョンで定義されたデフォルト値を参照します。送信者のコードで定義されたデフォルト値を参照するわけではありません。
  • enum は、ワイヤ形式の点で int32uint32int64、および uint64 と互換性があります (値が収まらない場合は切り捨てられることに注意してください)。ただし、メッセージがデシリアライズされるときに、クライアントコードがそれらを異なる方法で処理する場合があることに注意してください。特に、認識されない enum 値はメッセージがデシリアライズされるときに破棄されます。これにより、フィールドの has.. アクセサは false を返し、そのゲッターは enum 定義にリストされている最初の値、またはデフォルト値 (指定されている場合) を返します。repeated enum フィールドの場合、認識されない値はリストから削除されます。ただし、整数フィールドは常にその値を保持します。このため、整数を enum にアップグレードする際には、範囲外の enum 値をワイヤ上で受信することに非常に注意する必要があります。
  • 現在の Java および C++ 実装では、認識されない enum 値が削除されると、それらは他の不明なフィールドとともに保存されます。このデータがシリアライズされ、これらの値を認識するクライアントによって再解析される場合、奇妙な動作が発生する可能性があることに注意してください。optional フィールドの場合、元のメッセージがデシリアライズされた後に新しい値が書き込まれた場合でも、古い値はそれを認識するクライアントによって引き続き読み取られます。repeated フィールドの場合、古い値は、認識されて新しく追加された値の後に表示されます。つまり、順序は保持されません。
  • 単一の optional フィールドまたは拡張機能を新しい oneof のメンバーに変更することはバイナリ互換性がありますが、一部の言語 (特に Go) では、生成されたコードの API は互換性のない方法で変更されます。このため、Google は AIP-180 に記載されているように、パブリック API でそのような変更を行いません。ソース互換性に関する同じ注意点として、複数のフィールドを新しい oneof に移動することは、一度に複数のフィールドを設定するコードがないことを確信している場合は安全な場合があります。フィールドを既存の oneof に移動することは安全ではありません。同様に、単一のフィールド oneofoptional フィールドまたは拡張機能に変更することは安全です。
  • フィールドを map<K, V> と対応する repeated メッセージフィールドの間で変更することはバイナリ互換性があります (マップ、以下のメッセージレイアウトおよびその他の制限事項を参照)。ただし、変更の安全性はアプリケーションに依存します。メッセージをデシリアライズして再シリアライズするとき、repeated フィールド定義を使用するクライアントは意味的に同一の結果を生成します。ただし、map フィールド定義を使用するクライアントは、エントリを並べ替えたり、重複するキーを持つエントリを削除したりする場合があります。

不明なフィールド

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

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

不明なフィールドの保持

一部のアクションでは、不明なフィールドが失われる可能性があります。たとえば、次のいずれかを行うと、不明なフィールドが失われます。

  • proto を 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];
}

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

proto のほとんどのインスタンスで設定される標準フィールドには、フィールド番号 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 エコシステム外部のメカニズムを使用して、選択した拡張機能範囲内で拡張機能フィールド番号を割り当てます。1 つの例として、モノレポのコミット番号を使用することが考えられます。このシステムは、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 のスコープ内で宣言されているということです。それは単なる静的メンバーです。

一般的なパターンは、拡張機能のフィールドタイプのスコープ内で拡張機能を定義することです。たとえば、型 puppies.Photomedia.UserContent への拡張機能があり、拡張機能が 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

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

Oneof フィールドは optional フィールドに似ていますが、oneof 内のすべてのフィールドはメモリを共有し、同時に設定できるフィールドは最大で 1 つです。oneof のメンバーを設定すると、他のすべてのメンバーが自動的にクリアされます。選択した言語に応じて、特別な case() または WhichOneof() メソッドを使用して、oneof でどの値が設定されているか (設定されている場合) を確認できます。

複数の値が設定されている場合、proto の順序によって決定される最後に設定された値が以前のすべての値を上書きすることに注意してください。

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 キーワードは使用できません。repeated フィールドを oneof に追加する必要がある場合は、repeated フィールドを含むメッセージを使用できます。

生成されたコードでは、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++ を使用している場合は、コードがメモリクラッシュを引き起こさないようにしてください。次のサンプルコードは、sub_messageset_name() メソッドの呼び出しによってすでに削除されているため、クラッシュします。

    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 のメンバーであるかどうかを知る方法がないため、違いを区別する方法はありません。

タグの再利用に関する問題

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

マップ

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

map<key_type, value_type> map_field = N;

…ここで、key_type は任意の整数型または文字列型 (つまり、浮動小数点型と bytes を除く任意の スカラー 型) にすることができます。enum も proto メッセージも key_type には有効ではないことに注意してください。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;

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

パッケージ

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

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

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

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

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

  • C++ では、生成されたクラスは C++ 名前空間内にラップされます。たとえば、Open は名前空間 foo::bar になります。
  • Java および Kotlin では、.proto ファイルで option java_package を明示的に指定しない限り、パッケージは Java パッケージとして使用されます。
  • Python では、Python モジュールはファイルシステム内の場所に従って編成されるため、package ディレクティブは無視されます。
  • Go では、package ディレクティブは無視され、生成された .pb.go ファイルは、対応する go_proto_library Bazel ルールにちなんで名付けられたパッケージにあります。オープンソースプロジェクトの場合は、go_package オプションを指定するか、Bazel -M フラグを設定する必要があります
  • Ruby では、生成されたクラスはネストされた Ruby 名前空間内にラップされ、必要な Ruby 大文字と小文字のスタイルに変換されます (最初の文字は大文字、最初の文字が文字でない場合は PB_ が先頭に追加されます)。たとえば、Open は名前空間 Foo::Bar になります。
  • PHP では、.proto ファイルで option php_namespace を明示的に指定しない限り、パッケージは PascalCase に変換された後の名前空間として使用されます。たとえば、Open は名前空間 Foo\Bar になります。
  • C# では、.proto ファイルで option csharp_namespace を明示的に指定しない限り、パッケージは PascalCase に変換された後の名前空間として使用されます。たとえば、Open は名前空間 Foo.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 に転送します。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 システムをプラグインしたくない場合は、gRPC を使用できます。これは、Google で開発された言語およびプラットフォームに依存しないオープンソースの RPC システムです。gRPC はプロトコルバッファーと特に相性が良く、特別なプロトコルバッファーコンパイラプラグインを使用して、.proto ファイルから関連する RPC コードを直接生成できます。ただし、proto2 と proto3 で生成されたクライアントとサーバーの間には互換性の問題が発生する可能性があるため、gRPC サービスを定義するには proto3 または edition 2023 を使用することをお勧めします。proto3 構文の詳細については、Proto3 言語ガイド を、edition 2023 の詳細については、Edition 2023 言語ガイド を参照してください。

gRPC に加えて、プロトコルバッファー用の 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/などは、ネストされたクラス/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: プロトコルバッファーコンパイラは、最小限のクラスを生成し、シリアライズ、解析、およびその他のさまざまな操作を実装するために共有の reflection ベースのコードに依存します。したがって、生成されたコードは SPEED よりもはるかに小さくなりますが、操作は遅くなります。クラスは、SPEED モードの場合とまったく同じパブリック API を引き続き実装します。このモードは、非常に多数の .proto ファイルが含まれており、それらのすべてを非常に高速にする必要がないアプリで最も役立ちます。
    • LITE_RUNTIME: プロトコルバッファーコンパイラは、「lite」ランタイムライブラリ (libprotobuf の代わりに libprotobuf-lite) のみに依存するクラスを生成します。lite ランタイムは、完全なライブラリよりもはるかに小さく (約 1 桁小さい)、記述子やリフレクションなどの特定の機能を省略します。これは、携帯電話などの制約のあるプラットフォームで実行されているアプリに特に役立ちます。コンパイラは、SPEED モードと同様に、すべてのメソッドの高速実装を引き続き生成します。生成されたクラスは、各言語で Message インターフェイスのメソッドのサブセットのみを提供する MessageLite インターフェイスのみを実装します。
    option optimize_for = CODE_SIZE;
    
  • cc_generic_servicesjava_generic_servicespy_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 データを無視していました。したがって、既存のフィールドを wire 互換性を損なうことなく packed 形式に変更することはできませんでした。2.3.0 以降では、packable フィールドのパーサーは常に両方の形式を受け入れるため、この変更は安全ですが、古い protobuf バージョンを使用する古いプログラムを扱う場合は注意が必要です。

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

    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 = RETENTION_SOURCE を設定できます。これはソース保持と呼ばれます。

オプション保持は、ほとんどのユーザーが気にする必要のない高度な機能ですが、バイナリに保持するコードサイズのコストをかけずに特定のオプションを使用したい場合に役立ちます。ソース保持を持つオプションは、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# コードを生成するには、protocol buffer コンパイラー protoc.proto ファイルで実行する必要があります。コンパイラーをインストールしていない場合は、パッケージをダウンロードし、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 の短縮形として使用できます。

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

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

  • 入力として 1 つ以上の .proto ファイルを指定する必要があります。複数の .proto ファイルを一度に指定できます。ファイルは現在のディレクトリを基準とした名前が付けられていますが、各ファイルは IMPORT_PATH のいずれかに存在する必要があります。これにより、コンパイラーは正規名を決定できます。

ファイルの場所

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

場所は言語に依存しないようにする

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

このルールの例外は、protos がテストなどの Java コンテキストでのみ使用されることが明らかな場合です。

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

詳細については、