言語ガイド (proto 2)
このガイドでは、.protoファイルの構文や、.protoファイルからデータアクセス クラスを生成する方法など、プロトコルバッファ言語を使用してプロトコルバッファデータを構造化する方法について説明します。プロトコルバッファ言語のproto2リビジョンについて説明します。
エディション構文については、Protobuf Editions 言語ガイドを参照してください。
proto3構文については、Proto3 言語ガイドを参照してください。
これはリファレンスガイドです。このドキュメントで説明されている多くの機能を使用したステップバイステップの例については、選択した言語のチュートリアルを参照してください。
メッセージ型の定義
まず、非常に簡単な例を見てみましょう。検索リクエストメッセージ形式を定義したいとします。各検索リクエストには、クエリ文字列、関心のある結果の特定のページ、および1ページあたりの結果数が含まれています。これが、メッセージタイプを定義するために使用する.protoファイルです。
syntax = "proto2";
message SearchRequest {
optional string query = 1;
optional int32 page_number = 2;
optional int32 results_per_page = 3;
}
ファイルの最初の行は、protobuf 言語仕様の proto2 リビジョンを使用していることを指定します。
syntaxは、ファイルの最初の空でない、コメントでない行でなければなりません。syntaxが指定されていない場合、プロトコルバッファコンパイラは proto2 を使用していると仮定します。
SearchRequestメッセージ定義は、3つのフィールド(名前/値のペア)を指定しています。これらは、このタイプのメッセージに含めたい各データに対応しています。各フィールドには名前と型があります。
フィールド型の指定
前の例では、すべてのフィールドがスカラー型でした。2つの整数 (page_number と results_per_page) と1つの文字列 (query) です。フィールドには、列挙型や、他のメッセージ型のような複合型を指定することもできます。
フィールド番号の割り当て
メッセージ定義の各フィールドには、1から536,870,911までの番号を、以下の制限付きで与える必要があります。
- 指定された番号は、そのメッセージのすべてのフィールドの中で一意でなければなりません。
- フィールド番号
19,000から19,999は、Protocol Buffersの実装のために予約されています。メッセージでこれらの予約済みフィールド番号を使用すると、プロトコルバッファコンパイラは文句を言います。 - 以前に予約されたフィールド番号や、拡張機能に割り当てられたフィールド番号は使用できません。
この番号は、メッセージワイヤーフォーマットでフィールドを識別するため、メッセージタイプが使用されてから変更することはできません。「フィールド番号の変更」は、そのフィールドを削除し、同じタイプで新しい番号の新しいフィールドを作成することと同じです。これを適切に行う方法については、フィールドの削除を参照してください。
フィールド番号は決して再利用すべきではありません。予約済みリストからフィールド番号を取り出して、新しいフィールド定義で再利用しないでください。フィールド番号の再利用による影響を参照してください。
最も頻繁に設定されるフィールドには、フィールド番号1から15を使用する必要があります。フィールド番号の値が小さいほど、ワイヤーフォーマットで占めるスペースが少なくなります。たとえば、1から15の範囲のフィールド番号は、エンコードに1バイトかかります。16から2047の範囲のフィールド番号は2バイトかかります。これについては、プロトコルバッファエンコードで詳しく知ることができます。
フィールド番号の再利用による影響
フィールド番号を再利用すると、ワイヤーフォーマットメッセージのデコードが曖昧になります。
Protobufのワイヤーフォーマットは無駄がなく、ある定義でエンコードされたフィールドを別の定義でデコードしたことを検出する方法を提供していません。
ある定義を使用してフィールドをエンコードし、その後、その同じフィールドを別の定義でデコードすると、以下のような事態につながる可能性があります。
- デバッグに費やされる開発者の時間
- パース/マージエラー(最良のシナリオ)
- 個人情報/機密情報の漏洩
- データの破損
フィールド番号の再利用の一般的な原因
フィールドの番号変更(見た目の美しい番号順にするために行われることがあります)。番号変更は、事実上、番号変更に関わるすべてのフィールドを削除して再追加することになり、互換性のないワイヤーフォーマットの変更をもたらします。
フィールドを削除し、将来の再利用を防ぐために番号を予約しないこと。
フィールド番号は32ビットではなく29ビットに制限されています。これは、3ビットがフィールドのワイヤーフォーマットを指定するために使用されるためです。詳細については、エンコーディングのトピックを参照してください。
フィールドカーディナリティの指定
メッセージフィールドは、次のいずれかになります。
単数形:
proto2には、2種類の単一フィールドがあります。
optional: (推奨)optionalフィールドは、2つの可能な状態のいずれかです。- フィールドが設定されており、明示的に設定されたか、ワイヤーから解析された値が含まれています。ワイヤーにシリアライズされます。
- フィールドが設定されておらず、デフォルト値を返します。ワイヤーにシリアライズされません。
値が明示的に設定されたかどうかを確認できます。
required: 使用しないでください。必須フィールドは問題が多く、proto3とエディションから削除されました。必須フィールドのセマンティクスは、アプリケーション層で実装する必要があります。使用された場合、整形式のメッセージにはこのフィールドが正確に1つ存在する必要があります。
repeated: このフィールド型は、整形式のメッセージ内で0回以上繰り返すことができます。繰り返された値の順序は保持されます。map: これはキーと値のペアのフィールド型です。このフィールド型の詳細については、マップを参照してください。
新しい繰り返しフィールドにはパックエンコーディングを使用する
歴史的な理由により、スカラー数値型 (例: int32, int64, enum) のrepeatedフィールドは、可能であるほど効率的にエンコードされていません。新しいコードでは、より効率的なエンコーディングを得るために、特別なオプション[packed = true]を使用する必要があります。例:
repeated int32 samples = 4 [packed = true];
repeated ProtoEnum results = 5 [packed = true];
`packed`エンコーディングについての詳細は、Protocol Bufferエンコーディングで確認できます。
必須は強く非推奨
重要
必須は永遠です。前述のとおり、新しいフィールドにはrequiredを使用しないでください。必須フィールドのセマンティクスは、代わりにアプリケーション層で実装する必要があります。既存のrequiredフィールドは、メッセージ定義の永続的で不変な要素として扱う必要があります。フィールドをrequiredからoptionalに安全に変更することはほぼ不可能です。古いリーダーが存在する可能性がある場合、このフィールドがないメッセージを不完全とみなし、拒否または破棄する可能性があります。必須フィールドに関する2番目の問題は、誰かが列挙型に値を追加した場合に発生します。この場合、認識されない列挙値は欠落しているかのように扱われ、必須値のチェックも失敗します。
整形式メッセージ
「整形式」という用語は、protobufメッセージに適用される場合、シリアライズ/デシリアライズされたバイトを指します。protocパーサーは、与えられたproto定義ファイルが解析可能であることを検証します。
単一フィールドは、ワイヤフォーマットバイトに複数回出現する可能性があります。パーサーは入力を受け入れますが、生成されたバインディングを介してアクセスできるのはそのフィールドの最後のインスタンスのみです。このトピックの詳細については、最後が優先を参照してください。
メッセージ型の追加
単一の.protoファイルで複数のメッセージタイプを定義できます。これは、複数の関連メッセージを定義する場合に便利です。たとえば、SearchResponseメッセージタイプに対応する応答メッセージ形式を定義したい場合、同じ.protoに追加できます。
message SearchRequest {
optional string query = 1;
optional int32 page_number = 2;
optional int32 results_per_page = 3;
}
message SearchResponse {
...
}
メッセージの結合は肥大化につながる 複数のメッセージ型 (メッセージ、列挙型、サービスなど) を単一の .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` コンパイラの動作のみであり、ランタイムの動作には影響しません。ただし、例外として、TextProto の実装では、パース時に予約名を持つ不明なフィールドを破棄する場合があります(他の不明なフィールドのようにエラーを発生させずに)。(現在のところ、C++ と Go の実装のみがそのように動作します)。ランタイムの JSON パースは予約名の影響を受けません。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
同じ`reserved`文にフィールド名とフィールド番号を混在させることはできないことに注意してください。
.protoから何が生成されるか
.proto ファイルに対してプロトコルバッファコンパイラを実行すると、コンパイラは、ファイルで記述したメッセージタイプを操作するために必要なコードを選択した言語で生成します。これには、フィールド値の取得と設定、メッセージの出力ストリームへのシリアル化、入力ストリームからのメッセージのパースが含まれます。
- C++の場合、コンパイラは各`.proto`から`.h`と`.cc`ファイルを生成し、ファイルで記述された各メッセージ型に対応するクラスを作成します。
- Javaの場合、コンパイラは各メッセージ型に対応するクラスを含む`.java`ファイルを生成します。また、メッセージクラスのインスタンスを作成するための特別な`Builder`クラスも生成します。
- Kotlinでは、Java生成コードに加えて、コンパイラは各メッセージタイプに対して改善されたKotlin APIを持つ
.ktファイルを生成します。これには、メッセージインスタンスの作成を簡素化するDSL、nullableフィールドアクセサ、およびコピー関数が含まれます。 - Pythonは少し異なり、Python コンパイラは、
.proto内の各メッセージタイプの静的記述子を含むモジュールを生成し、それが実行時に必要なPythonデータアクセス クラスを作成するために*メタクラス*とともに使用されます。 - Goの場合、コンパイラは`.pb.go`ファイルを生成し、そのファイル内の各メッセージ型に対応する型を作成します。
- Rubyの場合、コンパイラはメッセージ型を含むRubyモジュールを持つ`.rb`ファイルを生成します。
- Objective-Cの場合、コンパイラは各`.proto`から`pbobjc.h`と`pbobjc.m`ファイルを生成し、ファイルで記述された各メッセージ型に対応するクラスを作成します。
- C#の場合、コンパイラは各`.proto`から`.cs`ファイルを生成し、ファイルに記述された各メッセージ型に対応するクラスを作成します。
- PHPの場合、コンパイラは、ファイルで記述された各メッセージ型に対して
.phpメッセージファイルを生成し、コンパイルする各.protoファイルに対して.phpメタデータファイルを生成します。メタデータファイルは、有効なメッセージ型を記述子プールにロードするために使用されます。 - Dartの場合、コンパイラはファイル内の各メッセージタイプに対応するクラスを持つ`.pb.dart`ファイルを生成します。
各言語のAPIの使用方法についての詳細は、選択した言語のチュートリアルを参考にしてください。さらに詳細なAPI情報については、関連するAPIリファレンスを参照してください。
スカラー値型
スカラーメッセージフィールドは以下のいずれかの型を持つことができます。表は`.proto`ファイルで指定された型と、自動生成されるクラスでの対応する型を示しています。
| Proto 型 | 注釈 |
|---|---|
| double | |
| float | |
| int32 | 可変長エンコーディングを使用します。負の数のエンコードには非効率です。フィールドが負の値を持つ可能性が高い場合は、代わりにsint32を使用してください。 |
| int64 | 可変長エンコーディングを使用します。負の数のエンコードには非効率です。フィールドが負の値を持つ可能性が高い場合は、代わりにsint64を使用してください。 |
| uint32 | 可変長エンコーディングを使用します。 |
| uint64 | 可変長エンコーディングを使用します。 |
| sint32 | 可変長エンコーディングを使用します。符号付き整数値。これらは通常のint32よりも効率的に負の数をエンコードします。 |
| sint64 | 可変長エンコーディングを使用します。符号付き整数値。これらは通常のint64よりも効率的に負の数をエンコードします。 |
| fixed32 | 常に4バイト。値が228より大きいことが多い場合、uint32より効率的です。 |
| fixed64 | 常に8バイト。値が256より大きいことが多い場合、uint64より効率的です。 |
| sfixed32 | 常に4バイト。 |
| sfixed64 | 常に8バイト。 |
| bool | |
| string | 文字列は常にUTF-8エンコードまたは7ビットASCIIテキストを含んでいる必要があり、232より長くすることはできません。 |
| 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 | ユニコード (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/Kotlin混合コードベースでの互換性を確保するため、符号なし型であってもJavaの対応する型を使用します。
[2] Javaでは、符号なし32ビットおよび64ビット整数は、符号付きの対応物を使用して表現され、最上位ビットは単に符号ビットに格納されます。
[3] すべての場合において、フィールドに値を設定する際には、それが有効であることを確認するための型チェックが実行されます。
[4] 64ビットまたは符号なし32ビット整数は、デコード時には常にlongとして表現されますが、フィールドを設定する際にintが与えられた場合はintになることがあります。すべての場合において、設定される値は表現される型に収まる必要があります。[2]を参照してください。
[5] Proto2は通常、文字列フィールドのUTF-8の有効性をチェックすることはありません。ただし、動作は言語によって異なり、無効なUTF-8データは文字列フィールドに保存すべきではありません。
[6] 64ビットマシンではIntegerが使用され、32ビットマシンではstringが使用されます。
メッセージをシリアライズする際にこれらの型がどのようにエンコードされるかについての詳細は、Protocol Bufferエンコーディングで確認できます。
フィールドのデフォルト値
メッセージがパースされる際、エンコードされたメッセージバイトに特定のフィールドが含まれていない場合、パースされたオブジェクトでそのフィールドにアクセスすると、そのフィールドのデフォルト値が返されます。デフォルト値は型によって異なります。
- 文字列の場合、デフォルト値は空文字列です。
- bytesの場合、デフォルト値は空のバイト列です。
- boolの場合、デフォルト値はfalseです。
- 数値型の場合、デフォルト値はゼロです。
- メッセージフィールドの場合、フィールドは設定されていません。その正確な値は言語に依存します。詳細については、ご使用の言語の生成コードガイドを参照してください。
- 列挙型の場合、デフォルト値は最初に定義された列挙値であり、0であるべきです(オープンな列挙型との互換性のために推奨されます)。列挙型のデフォルト値を参照してください。
repeatedフィールドのデフォルト値は空です(通常、適切な言語での空のリスト)。
マップフィールドのデフォルト値は空です(通常、適切な言語での空のマップ)。
デフォルトのスカラー値のオーバーライド
proto2では、単一の非メッセージフィールドに対して明示的なデフォルト値を指定できます。たとえば、SearchRequest.results_per_pageフィールドにデフォルト値10を指定したいとします。
optional int32 results_per_page = 3 [default = 10];
送信者が results_per_page を指定しなかった場合、受信者は次の状態を観測します。
results_per_pageフィールドは存在しません。つまり、has_results_per_page()(haszer メソッド) メソッドはfalseを返します。results_per_pageの値("getter"から返される)は10です。
送信者が results_per_page の値を送信した場合、デフォルト値の 10 は無視され、送信者の値が「getter」から返されます。
生成されたコードにおけるデフォルト値の動作に関する詳細は、選択した言語の生成コードガイドを参照してください。
列挙型のデフォルト値は最初に定義された列挙値であるため、列挙値リストの先頭に値を追加する場合は注意が必要です。定義を安全に変更する方法のガイドラインについては、メッセージ型の更新セクションを参照してください。
列挙型
メッセージタイプを定義する場合、そのフィールドの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;
}
列挙型のデフォルト値
`SearchRequest.corpus`フィールドのデフォルト値は`CORPUS_UNSPECIFIED`です。なぜなら、それがenumで定義された最初の値だからです。
すべての列挙型の最初の値を ENUM_TYPE_NAME_UNSPECIFIED = 0; または ENUM_TYPE_NAME_UNKNOWN = 0; として定義することを強く推奨します。これは、proto2 が列挙型フィールドの不明な値を処理する方法に起因します。
また、この最初のデフォルト値は、「この値は指定されなかった」以外の意味を持たないことが推奨されます。
SearchRequest.corpusフィールドのような列挙型フィールドのデフォルト値は、次のように明示的にオーバーライドできます。
optional Corpus corpus = 4 [default = CORPUS_UNIVERSAL];
列挙値のエイリアス
同じ値を異なる 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がどのように動作すべきかと、異なる言語で現在どのように動作するかの対比については、Enumの動作を参照してください。列挙値を削除することは、永続化されたプロトにとって破壊的な変更です。値を削除する代わりに、reservedキーワードで値をマークして、列挙値がコード生成されないようにするか、値を保持しつつ、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`を扱う方法についての詳細は、選択した言語の生成コードガイドを参照してください。
予約値
列挙型を完全に削除したり、コメントアウトしたりして更新すると、将来のユーザーは、その型に対する独自の更新を行う際に、数値値を再利用できます。これは、後で同じ.protoの古いインスタンスをロードした場合に、データの破損、プライバシーのバグなど、重大な問題を引き起こす可能性があります。これを確実に防ぐ1つの方法は、削除されたエントリの数値値(および/または名前。JSONシリアル化にも問題を引き起こす可能性があります)をreservedとして指定することです。プロトコルバッファコンパイラは、将来のユーザーがこれらの識別子を使用しようとすると文句を言います。maxキーワードを使用して、予約された数値範囲が最大可能値までであることを指定できます。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
同じ`reserved`文にフィールド名と数値を混在させることはできません。
他のメッセージ型の使用
他のメッセージタイプをフィールドタイプとして使用できます。たとえば、各SearchResponseメッセージにResultメッセージを含めたい場合、同じ.protoでResultメッセージタイプを定義し、SearchResponseでResult型のフィールドを指定することでこれを行うことができます。
message SearchResponse {
repeated Result results = 1;
}
message Result {
optional string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
定義のインポート
先の例では、`Result`メッセージ型は`SearchResponse`と同じファイルで定義されています。フィールド型として使用したいメッセージ型が既に別の`.proto`ファイルで定義されている場合はどうなるでしょうか。
他の`.proto`ファイルからの定義を*インポート*することで使用できます。別の`.proto`の定義をインポートするには、ファイルの先頭にimport文を追加します。
import "myproject/other_protos.proto";
デフォルトでは、直接インポートされた .proto ファイルからのみ定義を使用できます。しかし、.proto ファイルを新しい場所に移動する必要がある場合もあります。.proto ファイルを直接移動して、すべての呼び出し箇所を単一の変更で更新する代わりに、古い場所にプレースホルダーの .proto ファイルを配置し、import public の概念を使用してすべてのインポートを新しい場所に転送できます。
公開インポート機能は、Java、Kotlin、TypeScript、JavaScript、GCL、およびプロトバッファ静的リフレクションを使用するC++ターゲットでは利用できません。
`import public`の依存関係は、`import public`文を含むプロトをインポートする任意のコードによって推移的に依存される可能性があります。例えば:
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
プロトコルコンパイラは、-I/--proto_path フラグを使用してプロトコルコンパイラのコマンドラインで指定されたディレクトリのセット内でインポートされたファイルを検索します。フラグが指定されていない場合、コンパイラが呼び出されたディレクトリ内で検索します。一般的に、--proto_path フラグはプロジェクトのルートに設定し、すべてのインポートに完全修飾名を使用する必要があります。
proto3 メッセージタイプの使用
proto3 と edition 2023 のメッセージ型をインポートして、proto2 メッセージで使用できます。その逆も可能です。ただし、proto2 の列挙型は 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とまったく同じです。
メッセージタイプの更新
既存のメッセージタイプがすべてのニーズを満たさなくなった場合、例えば、メッセージ形式に余分なフィールドを追加したい場合でも、古い形式で作成されたコードを使用したい場合でも、ご心配なく!バイナリワイヤー形式を使用すれば、既存のコードを壊すことなくメッセージタイプを非常に簡単に更新できます。
注意
ProtoJSON または proto テキスト形式を使用してプロトコルバッファメッセージを保存する場合、proto 定義で行える変更は異なります。ProtoJSON のワイヤー形式の安全な変更については、こちらで説明されています。Protoのベストプラクティスと以下のルールを確認してください。
バイナリワイヤー形式で安全でない変更
ワイヤー非安全な変更とは、古いスキーマを使用してシリアライズされたデータを、新しいスキーマを使用するパーサー(またはその逆)でパースした場合に破損するスキーマ変更のことです。データのすべてのシリアライザーとデシリアライザーが新しいスキーマを使用していることがわかっている場合にのみ、ワイヤー非安全な変更を行ってください。
- 既存のフィールドのフィールド番号を変更することは安全ではありません。
- フィールド番号を変更することは、そのフィールドを削除し、同じ型で新しいフィールドを追加することと同等です。フィールドの番号を付け替えたい場合は、フィールドの削除の手順を参照してください。
- フィールドを既存の`oneof`に移動することは安全ではありません。
バイナリワイヤー形式で安全な変更
ワイヤーセーフな変更とは、データの損失や新たなパース失敗のリスクなしに、スキーマをこの方法で進化させることが完全に安全な変更です。
ワイヤ互換性のある変更であっても、特定の言語におけるアプリケーションコードにとっては破壊的な変更となる可能性があることに注意してください。たとえば、既存のenumに値を追加することは、そのenumに対する網羅的なswitchを持つすべてのコードにとってコンパイルエラーとなります。そのため、Googleは公開メッセージに対してこれらの種類の変更の一部を避ける場合があります。AIPsには、これらの変更のうちどれが安全であるかに関するガイダンスが含まれています。
- 新しいフィールドの追加は安全です。
- フィールドの削除は安全です。
- 更新されたメッセージタイプでは、同じフィールド番号を再度使用してはなりません。代わりにフィールドの名前を変更することをお勧めします。たとえば、「OBSOLETE_」というプレフィックスを追加したり、将来の
.protoユーザーが誤って番号を再利用しないようにフィールド番号を予約したりすることができます。
- 更新されたメッセージタイプでは、同じフィールド番号を再度使用してはなりません。代わりにフィールドの名前を変更することをお勧めします。たとえば、「OBSOLETE_」というプレフィックスを追加したり、将来の
- enumに値を追加することは安全です。
- 単一の明示的な存在フィールドまたは拡張を**新しい** `oneof`のメンバーに変更することは安全です。
- 1つのフィールドのみを含む`oneof`を明示的な存在フィールドに変更することは安全です。
- フィールドを同じ番号と型の拡張に変更することは安全です。
バイナリワイヤー形式で互換性のある変更(条件付きで安全)
Wire-safeな変更とは異なり、Wire-compatibleとは、特定の変更の前後で同じデータをパースできることを意味します。ただし、この形の変更では、データのパースが損失を伴う可能性があります。たとえば、int32をint64に変更することは互換性のある変更ですが、INT32_MAXより大きい値が書き込まれた場合、int32として読み取るクライアントは数値の上位ビットを破棄します。
スキーマに互換性のある変更を加えることができるのは、システムへのロールアウトを慎重に管理する場合のみです。たとえば、int32 を int64 に変更しても、新しいスキーマがすべてのエンドポイントにデプロイされるまでは有効な int32 値のみを書き込み続け、その後により大きな値を書き込み始めるようにすることができます。
スキーマが組織外に公開されている場合、新しいスキーマの展開を管理して、異なる値の範囲がいつ安全に使用できるかを知ることができないため、一般的にワイヤー互換の変更を行うべきではありません。
- `int32`、`uint32`、`int64`、`uint64`、および`bool`はすべて互換性があります。
- ワイヤーから解析された数値が対応する型に収まらない場合、C++でその数値をその型にキャストした場合と同じ効果が得られます(たとえば、64ビットの数値がint32として読み取られると、32ビットに切り捨てられます)。
- `sint32`と`sint64`は互いに互換性がありますが、他の整数型とは互換性が*ありません*。
- 書き込まれた値が INT_MIN と INT_MAX の間に含まれる場合、どちらの型でも同じ値として解析されます。sint64 の値がその範囲外に書き込まれ、sint32 として解析された場合、varint は 32 ビットに切り詰められ、その後 zigzag デコードが行われます (これにより異なる値が観測されます)。
- `string`と`bytes`は、バイト列が有効なUTF-8である限り互換性があります。
- 埋め込みメッセージは、バイト列がメッセージのエンコードされたインスタンスを含んでいる場合、`bytes`と互換性があります。
- `fixed32`は`sfixed32`と互換性があり、`fixed64`は`sfixed64`と互換性があります。
- `string`、`bytes`、およびメッセージフィールドの場合、単数形は`repeated`と互換性があります。
- 繰り返しフィールドのシリアル化されたデータが入力として与えられた場合、このフィールドが単一であると期待するクライアントは、プリミティブ型フィールドの場合は最後の入力値を取得し、メッセージ型フィールドの場合はすべての入力要素をマージします。ただし、これは通常、ブール型や列挙型を含む数値型には**安全ではありません**。数値型の繰り返しフィールドは、デフォルトでpacked形式でシリアル化され、単一フィールドが期待される場合には正しく解析されません。
- `enum`は`int32`、`uint32`、`int64`、`uint64`と互換性があります。
- メッセージが逆シリアル化される際にクライアントコードが異なる方法で処理する可能性があることに注意してください。例えば、認識されないproto3の
enum値はメッセージ内に保持されますが、メッセージが逆シリアル化されたときにそれがどのように表現されるかは言語によって異なります。
- メッセージが逆シリアル化される際にクライアントコードが異なる方法で処理する可能性があることに注意してください。例えば、認識されないproto3の
- フィールドを `map<K, V>` と対応する `repeated` メッセージフィールドとの間で変更することは、バイナリ互換です(メッセージレイアウトやその他の制約については、下記の マップ を参照してください)。
- しかし、変更の安全性はアプリケーションに依存します。メッセージを逆シリアル化して再シリアル化する場合、
repeatedフィールド定義を使用するクライアントは意味的に同一の結果を生成します。しかし、mapフィールド定義を使用するクライアントは、エントリを並べ替えたり、重複キーを持つエントリを削除したりする可能性があります。
- しかし、変更の安全性はアプリケーションに依存します。メッセージを逆シリアル化して再シリアル化する場合、
未知のフィールド
不明なフィールドとは、パーサーが認識しないフィールドを表す、適切に形成されたプロトコルバッファシリアル化データです。たとえば、古いバイナリが新しいフィールドを含む新しいバイナリから送信されたデータを解析する場合、それらの新しいフィールドは古いバイナリでは不明なフィールドになります。
当初、proto3 メッセージは常にパース時に不明なフィールドを破棄していましたが、バージョン 3.5 で proto2 の動作と一致させるために、不明なフィールドの保持を再導入しました。バージョン 3.5 以降では、不明なフィールドはパース中に保持され、シリアル化された出力に含まれます。
未知のフィールドの保持
一部の操作により、未知のフィールドが失われる可能性があります。たとえば、次のいずれかを行うと、未知のフィールドは失われます。
- プロトをJSONにシリアライズする。
- メッセージ内のすべてのフィールドを反復処理して、新しいメッセージを生成する。
未知のフィールドを失わないようにするには、次のことを行ってください。
- バイナリを使用し、データ交換にテキスト形式を使用しないようにする。
- フィールドごとにコピーするのではなく、`CopyFrom()` や `MergeFrom()` などのメッセージ指向の API を使用してデータをコピーする。
TextFormatは少し特殊なケースです。TextFormatにシリアライズすると、未知のフィールドはフィールド番号を使用して出力されます。しかし、TextFormatデータをバイナリプロトに戻してパースすると、フィールド番号を使用するエントリがある場合に失敗します。
エクステンション
拡張機能は、コンテナメッセージの外部で定義されたフィールドです。通常、コンテナメッセージの.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
}
];
}
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エコシステム外部のメカニズムを使用します。例として、モノレポのコミット番号を使用することが考えられます。このシステムは、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
多くのオプションフィールドがあり、同時に最大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キーワードは使用できません。繰り返しフィールドをoneofに追加する必要がある場合は、繰り返しフィールドを含むメッセージを使用できます。
生成されたコードでは、oneof フィールドは通常のoptionalフィールドと同じゲッターとセッターを持ちます。また、oneof のどの値が設定されているか (もしあれば) をチェックするための特別なメソッドも取得できます。選択した言語の oneof API の詳細については、関連するAPI リファレンスを参照してください。
Oneofの機能
oneofフィールドを設定すると、oneofの他のすべてのメンバーが自動的にクリアされます。そのため、複数のoneofフィールドを設定した場合、最後に設定したフィールドのみが値を持ち続けます。
SampleMessage message; message.set_name("name"); CHECK(message.has_name()); // Calling mutable_sub_message() will clear the name field and will set // sub_message to a new instance of SubMessage with none of its fields set. message.mutable_sub_message(); CHECK(!message.has_name());パーサーがワイヤ上で同じ oneof の複数のメンバーを検出した場合、解析されたメッセージでは最後に検出されたメンバーのみが使用されます。ワイヤ上のデータを解析する際、バイトの先頭から開始し、次の値を評価し、以下の解析ルールを適用します。
まず、同じoneof内の*異なる*フィールドが現在設定されているかを確認し、設定されていればそれをクリアします。
次に、そのフィールドがoneof内にないかのように内容を適用します。
- プリミティブは、既に設定されている値を上書きします。
- メッセージは、すでに設定されている値にマージされます。
oneof では拡張機能はサポートされていません。
oneofは`repeated`にできません。
リフレクションAPIはoneofフィールドに対して機能します。
oneofフィールドをデフォルト値に設定した場合(例えば、int32のoneofフィールドを0に設定するなど)、そのoneofフィールドの「ケース」が設定され、値はワイヤー上でシリアライズされます。
C++を使用している場合は、コードがメモリクラッシュを引き起こさないように注意してください。以下のサンプルコードは、`set_name()`メソッドを呼び出すことによって`sub_message`がすでに削除されているため、クラッシュします。
SampleMessage message; SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here再びC++で、oneofを持つ2つのメッセージを`Swap()`すると、各メッセージは他方のoneofケースを持つことになります。以下の例では、`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 のメンバーであるかどうかを知る方法がないため、違いを区別する方法はありません。
タグの再利用に関する問題
- オプショナルフィールドを oneof に移動または oneof から移動する: メッセージがシリアル化および解析された後、情報の一部が失われる可能性があります (一部のフィールドがクリアされます)。ただし、単一フィールドを新しい oneof に安全に移動できます。また、常に1つしか設定されないことがわかっている場合は、複数のフィールドを移動できる場合があります。詳細については、メッセージ型の更新を参照してください。
- oneofフィールドを削除して再度追加する: メッセージがシリアライズされ、パースされた後、現在設定されているoneofフィールドがクリアされる可能性があります。
- oneof の分割または結合: これは、
optionalフィールドの移動と同様の問題があります。
マップ
データ定義の一部として連想マップを作成したい場合、プロトコルバッファは便利なショートカット構文を提供します。
map<key_type, value_type> map_field = N;
...ここで、key_typeは任意の整数型または文字列型(つまり、浮動小数点型とbytesを除くすべてのスカラー型)になります。key_typeにはenum型もプロトメッセージも有効ではありません。value_typeは別のマップを除く任意の型になります。
したがって、たとえば、各`Project`メッセージが文字列キーに関連付けられているプロジェクトのマップを作成したい場合、次のように定義できます。
map<string, Project> projects = 3;
マップの機能
- マップでは拡張機能はサポートされていません。
- マップは
repeated、optional、またはrequiredにすることはできません。 - マップ値のワイヤーフォーマットの順序とマップの反復順序は未定義であるため、マップ項目が特定の順序であることに依存することはできません。
- `.proto`のテキストフォーマットを生成する際、マップはキーによってソートされます。数値キーは数値的にソートされます。
- ワイヤーからパースする場合やマージする場合、重複したマップキーが存在すると、最後に見つかったキーが使用されます。テキストフォーマットからマップをパースする場合、重複したキーがあるとパースに失敗することがあります。
- マップフィールドにキーは提供するが値を提供しない場合、フィールドがシリアライズされる際の動作は言語に依存します。C++、Java、Kotlin、Pythonでは型のデフォルト値がシリアライズされますが、他の言語では何もシリアライズされません。
- シンボル `FooEntry` は、マップ `foo` と同じスコープに存在できません。なぜなら、`FooEntry` はマップの実装によってすでに使用されているからです。
生成されたマップAPIは、現在サポートされているすべての言語で利用可能です。選択した言語のマップAPIの詳細については、関連するAPIリファレンスで確認できます。
後方互換性
マップ構文は、ワイヤー上では以下のものと等価であるため、マップをサポートしていないプロトコルバッファ実装でもデータを処理できます。
message MapFieldEntry {
optional key_type key = 1;
optional value_type value = 2;
}
repeated MapFieldEntry map_field = N;
マップをサポートするプロトコルバッファ実装は、以前の定義で受け入れ可能なデータを生成し、受け入れる必要があります。
パッケージ
プロトコルメッセージタイプ間の名前の衝突を防ぐために、`.proto`ファイルにオプションの`package`指定子を追加できます。
package foo.bar;
message Open { ... }
その後、メッセージタイプのフィールドを定義する際にパッケージ指定子を使用できます。
message Foo {
...
optional foo.bar.Open open = 1;
...
}
パッケージ指定子が生成されたコードにどのように影響するかは、選択した言語によって異なります。
- C++では、生成されたクラスはC++の名前空間内にラップされます。たとえば、`Open`は`foo::bar`という名前空間に入ります。
- JavaおよびKotlinでは、`.proto`ファイルで明示的に`option java_package`を指定しない限り、パッケージはJavaのパッケージとして使用されます。
- Pythonでは、Pythonモジュールはファイルシステム内の場所によって整理されるため、`package`ディレクティブは無視されます。
- Goでは、
packageディレクティブは無視され、生成された.pb.goファイルは、対応するgo_proto_libraryBazelルールに従って命名されたパッケージに格納されます。オープンソースプロジェクトの場合、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名前空間にあります。
Python のように package ディレクティブが生成コードに直接影響しない場合でも、.proto ファイルにパッケージを指定することを強く推奨します。そうしないと、記述子で名前の競合が発生したり、他の言語とのプロトの移植性が損なわれたりする可能性があるためです。
パッケージと名前解決
プロトコル バッファ言語における型名の解決は C++ に似ています。最初に最も内側のスコープが検索され、次にその外側のスコープが検索されます。各パッケージは親パッケージの「内側」と見なされます。先頭の「.」(例: .foo.bar.Baz)は、代わりに最も外側のスコープから検索を開始することを意味します。
プロトコルバッファコンパイラは、インポートされた`.proto`ファイルを解析することですべての型名を解決します。各言語のコードジェネレータは、異なるスコーピングルールを持っていても、その言語で各型を参照する方法を知っています。
サービスの定義
RPC(Remote Procedure Call)システムでメッセージタイプを使用したい場合は、.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システムを接続したくない場合、Googleで開発された言語とプラットフォームに依存しないオープンソースのRPCシステムであるgRPCを使用できます。gRPCはプロトコルバッファと特に相性が良く、特別なプロトコルバッファコンパイラプラグインを使用して、.protoファイルから直接関連するRPCコードを生成できます。ただし、proto2とproto3で生成されたクライアントとサーバーの間には潜在的な互換性の問題があるため、gRPCサービスを定義するにはproto3またはエディション2023を使用することをお勧めします。proto3構文の詳細についてはProto3言語ガイドを、エディション2023についてはエディション2023言語ガイドを参照してください。
gRPCに加えて、プロトコルバッファのRPC実装を開発するためのサードパーティプロジェクトも多数進行中です。既知のプロジェクトへのリンクのリストについては、サードパーティアドオンwikiページを参照してください。
JSONマッピング
標準的なプロトバッファバイナリワイヤフォーマットは、プロトバッファを使用する2つのシステム間の通信に推奨されるシリアル化フォーマットです。プロトバッファワイヤフォーマットではなくJSONを使用するシステムと通信する場合、プロトバッファはJSONの正準エンコーディングをサポートしています。
オプション
.proto ファイル内の個々の宣言には、いくつかの*オプション*で注釈を付けることができます。オプションは、宣言の全体的な意味を変更しませんが、特定のコンテキストでの処理方法に影響を与える可能性があります。利用可能なオプションの完全なリストは、/google/protobuf/descriptor.proto で定義されています。
一部のオプションはファイルレベルのオプションであり、メッセージ、列挙型、サービス定義の内部ではなく、トップレベルのスコープに記述する必要があります。一部のオプションはメッセージレベルのオプションであり、メッセージ定義の内部に記述する必要があります。一部のオプションはフィールドレベルのオプションであり、フィールド定義の内部に記述する必要があります。オプションは、列挙型、列挙値、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ファイル用に生成された他のすべてのクラス/列挙型などは、この外部ラッパーJavaクラスの内部にネストされたクラス/列挙型などとして生成されます。Javaコードを生成しない場合、このオプションは効果がありません。option java_outer_classname = "Ponycopter";java_multiple_files(ファイルオプション):falseの場合、この.protoファイルに対して1つの.javaファイルのみが生成され、トップレベルのメッセージ、サービス、および列挙型に対して生成されたすべてのJavaクラス/列挙型などは、外部クラス(java_outer_classnameを参照)内にネストされます。trueの場合、トップレベルのメッセージ、サービス、および列挙型に対して生成された各Javaクラス/列挙型などに対して個別の.javaファイルが生成され、この.protoファイルに対して生成されたラッパーJavaクラスには、ネストされたクラス/列挙型などは含まれません。これはブール値オプションで、デフォルトはfalseです。Javaコードを生成しない場合、このオプションは効果がありません。option java_multiple_files = true;`optimize_for` (ファイルオプション): `SPEED`、`CODE_SIZE`、または`LITE_RUNTIME`に設定できます。これはC++およびJavaのコードジェネレータ(および場合によってはサードパーティのジェネレータ)に次のように影響します。
- `SPEED` (デフォルト): プロトコルバッファコンパイラは、メッセージタイプのシリアライズ、パース、その他の一般的な操作のためのコードを生成します。このコードは高度に最適化されています。
CODE_SIZE: プロトコルバッファコンパイラは最小限のクラスを生成し、シリアル化、パース、およびその他の様々な操作を実装するために、共有の反射ベースのコードに依存します。そのため、生成されるコードはSPEEDよりもはるかに小さくなりますが、操作は遅くなります。クラスは依然としてSPEEDモードと同じ公開APIを正確に実装します。このモードは、非常に多くの.protoファイルを含み、それらすべてが非常に高速である必要がないアプリで最も役立ちます。LITE_RUNTIME: プロトコルバッファコンパイラは、「lite」ランタイムライブラリ(libprotobufではなくlibprotobuf-lite)のみに依存するクラスを生成します。liteランタイムはフルライブラリよりもはるかに小さく(約1桁小さい)、記述子やリフレクションなどの特定の機能が省略されています。これは、携帯電話のような制約のあるプラットフォームで実行されるアプリに特に役立ちます。コンパイラは、SPEEDモードと同様に、すべてのメソッドの高速実装を生成します。生成されたクラスは、各言語でMessageLiteインターフェースのみを実装し、これはフルMessageインターフェースのメソッドのサブセットのみを提供します。
option optimize_for = CODE_SIZE;cc_generic_services、java_generic_services、py_generic_services(ファイルオプション): 汎用サービスは非推奨です。それぞれ C++、Java、Python で、サービス定義に基づいてプロトコルバッファコンパイラが抽象サービスコードを生成するかどうか。レガシーな理由から、これらはデフォルトでtrueです。しかし、バージョン 2.3.0 (2010年1月) 以降、RPC 実装は「抽象」サービスに依存するのではなく、各システムに特化したコードを生成するためのコードジェネレータプラグインを提供することが望ましいとされています。// This file relies on plugins to generate service code. option cc_generic_services = false; option java_generic_services = false; option py_generic_services = false;`cc_enable_arenas` (ファイルオプション): C++で生成されたコードに対してアリーナアロケーションを有効にします。
objc_class_prefix(ファイルオプション): この.protoから生成されるすべてのObjective-Cクラスと列挙型に前置される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以前のパーサーとの互換性が必要な場合です。これらの古いパーサーは、パックされたデータが期待されていない場合にそれを無視しました。そのため、ワイヤ互換性を壊さずに既存のフィールドをパック形式に変更することはできませんでした。2.3.0以降では、この変更は安全です。なぜなら、パック可能なフィールドのパーサーは常に両方の形式を受け入れるためですが、古いprotobufバージョンを使用する古いプログラムを扱う必要がある場合は注意してください。repeated int32 samples = 4 [packed = true];deprecated(フィールドオプション):trueに設定すると、そのフィールドが非推奨であり、新しいコードでは使用すべきではないことを示します。ほとんどの言語では、これによる実際の影響はありません。Javaでは、@Deprecatedアノテーションになります。C++では、非推奨のフィールドが使用されるたびにclang-tidyが警告を生成します。将来的には、他の言語固有のコードジェネレーターが、フィールドのアクセサに非推奨アノテーションを生成し、その結果、フィールドを使用しようとするコードをコンパイルする際に警告が発行される可能性があります。フィールドが誰にも使用されておらず、新しいユーザーが使用するのを防ぎたい場合は、フィールド宣言を予約ステートメントに置き換えることを検討してください。optional int32 old_field = 6 [deprecated = true];
列挙値のオプション
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値やフィールドにカスタムオプションを適用する方法については、カスタムオプションを参照してください。
カスタムオプション
プロトコルバッファは、独自のオプションを定義して使用することもできます。これは、ほとんどの人が必要としない高度な機能であることに注意してください。オプションは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]
カスタムオプションは、プロトコルバッファ言語のあらゆる種類の構造に定義できます。あらゆる種類のオプションを使用した例を次に示します。
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!";
}
最後に1つ:カスタムオプションは拡張機能であるため、他のフィールドや拡張機能と同様にフィールド番号を割り当てる必要があります。上記の例では、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が設定されている場合、そのフィールドは列挙型(または他の非メッセージエンティティ)のカスタムオプションに設定できません。Protocはこれを強制し、ターゲット制約違反がある場合はエラーを発生させます。
一見すると、この機能は不必要に思えるかもしれません。なぜなら、すべてのカスタムオプションは特定のエンティティのオプションメッセージの拡張機能であり、すでにそのオプションをその1つのエンティティに制約しているからです。しかし、オプションターゲットは、複数のエンティティタイプに適用される共有オプションメッセージがあり、そのメッセージ内の個々のフィールドの使用を制御したい場合に役立ちます。例えば:
message MyOptions {
optional string file_only_option = 1 [targets = TARGET_TYPE_FILE];
optional int32 message_and_enum_option = 2 [targets = TARGET_TYPE_MESSAGE,
targets = TARGET_TYPE_ENUM];
}
extend google.protobuf.FileOptions {
optional MyOptions file_options = 50000;
}
extend google.protobuf.MessageOptions {
optional MyOptions message_options = 50000;
}
extend google.protobuf.EnumOptions {
optional MyOptions enum_options = 50000;
}
// OK: this field is allowed on file options
option (file_options).file_only_option = "abc";
message MyMessage {
// OK: this field is allowed on both message and enum options
option (message_options).message_and_enum_option = 42;
}
enum MyEnum {
MY_ENUM_UNSPECIFIED = 0;
// Error: file_only_option cannot be set on an enum.
option (enum_options).file_only_option = "xyz";
}
クラスの生成
.proto ファイルで定義されたメッセージ型を操作するために必要な Java、Kotlin、Python、C++、Go、Ruby、Objective-C、または C# のコードを生成するには、.proto ファイルに対してプロトコルバッファコンパイラ protoc を実行する必要があります。コンパイラをインストールしていない場合は、パッケージをダウンロードし、README の指示に従ってください。Go の場合、コンパイラ用の特殊なコードジェネレータープラグインもインストールする必要があります。これは GitHub の golang/protobuf リポジトリで見つけることができ、インストール手順も記載されています。
プロトコルコンパイラは次のように起動します。
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
IMPORT_PATHは、importディレクティブを解決する際に.protoファイルを検索するディレクトリを指定します。省略された場合、現在のディレクトリが使用されます。複数のインポートディレクトリは、--proto_pathオプションを複数回渡すことで指定できます。それらは順番に検索されます。-I=_IMPORT_PATH_は--proto_pathの短縮形として使用できます。
注: proto_pathに対するファイルパスは、特定のバイナリ内でグローバルに一意でなければなりません。たとえば、proto/lib1/data.protoとproto/lib2/data.protoがある場合、-I=proto/lib1 -I=proto/lib2と一緒にこれらの2つのファイルを使用することはできません。なぜなら、import "data.proto"がどちらのファイルを意味するのか曖昧になるためです。代わりに-Iproto/を使用する必要があり、グローバル名はlib1/data.protoとlib2/data.protoになります。
ライブラリを公開し、他のユーザーが直接メッセージを使用する可能性がある場合は、ファイル名の衝突を避けるために、使用されることが想定されるパスに一意のライブラリ名を含める必要があります。1つのプロジェクトに複数のディレクトリがある場合は、プロジェクトの最上位ディレクトリに1つの-Iを設定することを推奨します。
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以外のコードが同じプロトを使用する場合、パスのプレフィックスはもはや意味をなしません。そのため、一般的には、プロトを//myteam/mypackageのような関連する言語に依存しないディレクトリに配置します。
このルールの例外は、プロトがJavaコンテキストでのみ使用されることが明確な場合、たとえばテスト用の場合です。
サポートされているプラットフォーム
に関する情報
- サポートされているオペレーティングシステム、コンパイラ、ビルドシステム、C++バージョンについては、Foundational C++ Support Policyを参照してください。
- サポートされているPHPバージョンについては、サポートされているPHPバージョンを参照してください。