言語ガイド (proto 2)
このガイドでは、.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_number
と results_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 エンコードを使用する
歴史的な理由から、スカラー数値型 (たとえば、int32
、int64
、enum
) の repeated
フィールドは、可能な限り効率的にエンコードされていません。新しいコードでは、より効率的なエンコードを取得するために、特別なオプション [packed = true]
を使用する必要があります。例:
repeated int32 samples = 4 [packed = true];
repeated ProtoEnum results = 5 [packed = true];
packed
エンコードの詳細については、「プロトコル バッファのエンコード」を参照してください。
Required は強く非推奨
重要
Required は永遠に 前述のように、required
は新しいフィールドには使用しないでください。必須フィールドのセマンティクスは、代わりにアプリケーション層で実装する必要があります。既存の required
フィールドは、メッセージ定義の永続的な不変の要素として扱う必要があります。フィールドを required
から optional
に安全に変更することはほぼ不可能です。古いリーダーが存在する可能性がある場合、このフィールドのないメッセージは不完全であると見なされ、拒否またはドロップされる可能性があります。必須フィールドの 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 11
は 9, 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 より長くすることはできません。 |
bytes | 232 を超えない任意のバイトシーケンスを含めることができます。 |
Proto の型 | C++ の型 | Java/Kotlin の型[1] | Python の型[3] | Go の型 | Ruby の型 | C# の型 | PHP の型 | Dart の型 | Rust の型 |
---|---|---|---|---|---|---|---|---|---|
double | double | double | float | *float64 | Float | double | float | double | f64 |
float | float | float | float | *float32 | Float | float | float | double | f32 |
int32 | int32_t | int | int | int32 | Fixnum または Bignum (必要に応じて) | int | integer | *int32 | i32 |
int64 | int64_t | long | int/long[4] | *int64 | Bignum | long | integer/string[6] | Int64 | i64 |
uint32 | uint32_t | int[2] | int/long[4] | *uint32 | Fixnum または Bignum (必要に応じて) | uint | integer | int | u32 |
uint64 | uint64_t | long[2] | int/long[4] | *uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 |
sint32 | int32_t | int | int | int32 | Fixnum または Bignum (必要に応じて) | int | integer | *int32 | i32 |
sint64 | int64_t | long | int/long[4] | *int64 | Bignum | long | integer/string[6] | Int64 | i64 |
fixed32 | uint32_t | int[2] | int/long[4] | *uint32 | Fixnum または Bignum (必要に応じて) | uint | integer | int | u32 |
fixed64 | uint64_t | long[2] | int/long[4] | *uint64 | Bignum | ulong | integer/string[6] | Int64 | u64 |
sfixed32 | int32_t | int | int | *int32 | Fixnum または Bignum (必要に応じて) | int | integer | int | i32 |
sfixed64 | int64_t | long | int/long[4] | *int64 | Bignum | long | integer/string[6] | Int64 | i64 |
bool | bool | boolean | bool | *bool | TrueClass/FalseClass | bool | boolean | bool | bool |
string | string | String | unicode (Python 2)、str (Python 3) | *string | String (UTF-8) | string | string | String | ProtoString |
bytes | string | ByteString | bytes | []byte | String (ASCII-8BIT) | ByteString | string | List | ProtoBytes |
[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 つだけを持つようにしたい場合があります。たとえば、各 SearchRequest
に corpus
フィールドを追加するとします。corpus は、UNIVERSAL
、WEB
、IMAGES
、LOCAL
、NEWS
、PRODUCTS
、または 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
クラスが含まれます。これは、ランタイム生成クラスで整数値を持つシンボリック定数のセットを作成するために使用されます。
重要
生成されたコードは、列挙子の数に関する言語固有の制限 (1 つの言語では数千) を受ける場合があります。使用する予定の言語の制限を確認してください。重要
enum が異なる言語で現在どのように動作するかと対照的に、どのように動作する必要があるかについては、「Enum の動作」を参照してください。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
メッセージを含めるとします。これを行うには、同じ .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 ステートメントを追加します。
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
とまったく同じです。
メッセージ型の更新
既存のメッセージ型がすべてのニーズを満たさなくなった場合 (たとえば、メッセージ形式に余分なフィールドが必要になった場合) でも、古い形式で作成されたコードを使用したい場合でも、心配はいりません。バイナリ ワイヤ形式を使用すると、既存のコードを壊すことなくメッセージ型を更新するのは非常に簡単です。
注
JSON または proto テキスト形式を使用してプロトコル バッファ メッセージを格納する場合、proto 定義で行うことができる変更は異なります。「Proto のベストプラクティス」と次のルールを確認してください。
- 既存のフィールドのフィールド番号を変更しないでください。フィールド番号の「変更」は、フィールドを削除し、同じ型で新しいフィールドを追加することと同じです。フィールドの番号を振り直す場合は、「フィールドの削除」の手順を参照してください。
- 追加する新しいフィールドは、
optional
またはrepeated
にする必要があります。これは、以前のメッセージ形式を使用するコードによってシリアライズされたメッセージでも、新しい生成コードで解析できることを意味します。required
要素が欠落することはないためです。これらの要素のデフォルト値を念頭に置いて、新しいコードが古いコードによって生成されたメッセージと適切にやり取りできるようにする必要があります。同様に、新しいコードによって作成されたメッセージは、古いコードによって解析できます。古いバイナリは、解析時に新しいフィールドを単に無視します。ただし、不明なフィールドは破棄されず、メッセージが後でシリアライズされる場合、不明なフィールドも一緒にシリアライズされます。したがって、メッセージが新しいコードに渡されると、新しいフィールドも引き続き使用できます。詳細については、不明なフィールドのセクションを参照してください。 - 必須ではないフィールドは、フィールド番号が更新されたメッセージタイプで再利用されない限り、削除できます。代わりにフィールドの名前を変更したり、たとえば「OBSOLETE_」というプレフィックスを追加したり、フィールド番号を予約済みにして、将来の
.proto
のユーザーが誤ってその番号を再利用しないようにすることができます。 - 必須ではないフィールドは、タイプと番号が同じままであれば、拡張機能に変換したり、その逆に変換したりできます。
int32
、uint32
、int64
、uint64
、およびbool
はすべて互換性があります。つまり、前方互換性または後方互換性を損なうことなく、これらのタイプのフィールドを別のタイプに変更できます。ワイヤから解析された数値が対応する型に収まらない場合、C++ でその型に数値をキャストした場合と同じ効果が得られます (たとえば、64 ビット数値が int32 として読み取られると、32 ビットに切り捨てられます)。sint32
とsint64
は互いに互換性がありますが、他の整数型とは互換性がありません。string
とbytes
は、バイトが有効な UTF-8 であれば互換性があります。- 埋め込みメッセージは、バイトにメッセージのエンコードされたインスタンスが含まれている場合、
bytes
と互換性があります。 fixed32
はsfixed32
と、fixed64
はsfixed64
と互換性があります。string
、bytes
、およびメッセージフィールドの場合、singular はrepeated
と互換性があります。repeated フィールドのシリアライズされたデータが入力として与えられた場合、このフィールドが singular であることを期待するクライアントは、それがプリミティブ型フィールドの場合は最後の入力値を取得し、メッセージ型フィールドの場合はすべての入力要素をマージします。これは、bool や enum を含む数値型には一般に安全ではないことに注意してください。数値型の repeated フィールドは、packed 形式でシリアライズされる場合があります。これは、singular フィールドが期待される場合には正しく解析されません。- デフォルト値を変更することは一般に問題ありません。ただし、デフォルト値はワイヤ経由で送信されないことを覚えておく必要があります。したがって、プログラムが特定のフィールドが設定されていないメッセージを受信した場合、プログラムはプロトコルのそのプログラムのバージョンで定義されたデフォルト値を参照します。送信者のコードで定義されたデフォルト値を参照するわけではありません。
enum
は、ワイヤ形式の点でint32
、uint32
、int64
、およびuint64
と互換性があります (値が収まらない場合は切り捨てられることに注意してください)。ただし、メッセージがデシリアライズされるときに、クライアントコードがそれらを異なる方法で処理する場合があることに注意してください。特に、認識されないenum
値はメッセージがデシリアライズされるときに破棄されます。これにより、フィールドのhas..
アクセサは false を返し、そのゲッターはenum
定義にリストされている最初の値、またはデフォルト値 (指定されている場合) を返します。repeated enum フィールドの場合、認識されない値はリストから削除されます。ただし、整数フィールドは常にその値を保持します。このため、整数をenum
にアップグレードする際には、範囲外の enum 値をワイヤ上で受信することに非常に注意する必要があります。- 現在の Java および C++ 実装では、認識されない
enum
値が削除されると、それらは他の不明なフィールドとともに保存されます。このデータがシリアライズされ、これらの値を認識するクライアントによって再解析される場合、奇妙な動作が発生する可能性があることに注意してください。optional フィールドの場合、元のメッセージがデシリアライズされた後に新しい値が書き込まれた場合でも、古い値はそれを認識するクライアントによって引き続き読み取られます。repeated フィールドの場合、古い値は、認識されて新しく追加された値の後に表示されます。つまり、順序は保持されません。 - 単一の
optional
フィールドまたは拡張機能を新しいoneof
のメンバーに変更することはバイナリ互換性がありますが、一部の言語 (特に Go) では、生成されたコードの API は互換性のない方法で変更されます。このため、Google は AIP-180 に記載されているように、パブリック API でそのような変更を行いません。ソース互換性に関する同じ注意点として、複数のフィールドを新しいoneof
に移動することは、一度に複数のフィールドを設定するコードがないことを確信している場合は安全な場合があります。フィールドを既存のoneof
に移動することは安全ではありません。同様に、単一のフィールドoneof
をoptional
フィールドまたは拡張機能に変更することは安全です。 - フィールドを
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 以上は、拡張機能の宣言でのみ使用できます。
拡張機能の型の指定
拡張機能は、oneof
と map
を除く任意のフィールドタイプにすることができます。
ネストされた拡張機能 (非推奨)
別のメッセージのスコープ内で拡張機能を宣言できます。
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_count
が puppies.Photo
のスコープ内で定義されることです。
これは一般的な混乱の元です。メッセージタイプ内にネストされた extend
ブロックを宣言しても、外側のタイプと拡張されたタイプの間に関係があるという意味にはなりません。特に、前の例は、Photo
が UserProfile
の何らかのサブクラスであることを意味するものではありません。それが意味するのは、シンボル likes_count
が Photo
のスコープ内で宣言されているということです。それは単なる静的メンバーです。
一般的なパターンは、拡張機能のフィールドタイプのスコープ内で拡張機能を定義することです。たとえば、型 puppies.Photo
の media.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
フィールドを除く任意のタイプのフィールドを追加できますが、required
、optional
、または 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_message
がset_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 ケースで終わります。以下の例では、msg1
はsub_message
を持ち、msg2
はname
を持ちます。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;
マップの機能
- 拡張機能はマップではサポートされていません。
- マップを
repeated
、optional
、または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.proto
はFooBar.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
(ファイルオプション):SPEED
、CODE_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_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 データを無視していました。したがって、既存のフィールドを 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
で定義されたメッセージ(FileOptions
や FieldOptions
など)によって定義されるため、独自のオプションを定義することは、これらのメッセージを拡張することにすぎません。例えば
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()
は MyMessage
の MessageOptions
プロトコルメッセージを返します。そこからカスタムオプションを読み取るのは、他の拡張機能を読み取るのと同じです。
同様に、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
を設定しようとしてもそれをオーバーライドできません。
注
Protocol Buffers 22.0 の時点では、オプション保持のサポートはまだ進行中であり、C++ と Java のみがサポートされています。Go は 1.29.0 以降でサポートされています。Python のサポートは完了していますが、まだリリースされていません。オプションのターゲット
フィールドには、オプションとして使用する場合にフィールドを適用できるエンティティのタイプを制御する 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 つ以上の出力ディレクティブを指定できます。
--cpp_out
はDST_DIR
に C++ コードを生成します。詳細については、C++ 生成コードリファレンスを参照してください。--java_out
はDST_DIR
に Java コードを生成します。詳細については、Java 生成コードリファレンスを参照してください。--kotlin_out
はDST_DIR
に追加の Kotlin コードを生成します。詳細については、Kotlin 生成コードリファレンスを参照してください。--python_out
はDST_DIR
に Python コードを生成します。詳細については、Python 生成コードリファレンスを参照してください。--go_out
はDST_DIR
に Go コードを生成します。詳細については、Go 生成コードリファレンスを参照してください。--ruby_out
はDST_DIR
に Ruby コードを生成します。詳細については、Ruby 生成コードリファレンスを参照してください。--objc_out
はDST_DIR
に Objective-C コードを生成します。詳細については、Objective-C 生成コードリファレンスを参照してください。--csharp_out
はDST_DIR
に C# コードを生成します。詳細については、C# 生成コードリファレンスを参照してください。--php_out
はDST_DIR
に PHP コードを生成します。詳細については、PHP 生成コードリファレンスを参照してください。
追加の利便性として、
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 コンテキストでのみ使用されることが明らかな場合です。
サポートされているプラットフォーム
詳細については、
- サポートされているオペレーティングシステム、コンパイラー、ビルドシステム、および C++ バージョンについては、Foundational C++ Support Policy を参照してください。
- サポートされている PHP バージョンについては、サポートされている PHP バージョン を参照してください。