言語ガイド(エディション)
このガイドでは、protocol buffer 言語を使用して protocol buffer データを構造化する方法について説明します。これには、.proto
ファイルの構文と、.proto
ファイルからデータ アクセス クラスを生成する方法が含まれます。このガイドは、protocol buffer 言語の edition 2023 を対象としています。エディションが proto2 および proto3 と概念的にどのように異なるかについては、Protobuf エディションの概要 を参照してください。
proto2 構文の詳細については、Proto2 言語ガイド を参照してください。
proto3 構文の詳細については、Proto3 言語ガイド を参照してください。
これはリファレンス ガイドです。このドキュメントで説明されている機能の多くを使用するステップバイステップの例については、選択した言語の チュートリアル を参照してください。
メッセージ型の定義
まず、非常に簡単な例を見てみましょう。検索リクエスト メッセージ形式を定義するとします。各検索リクエストには、クエリ文字列、関心のある結果の特定のページ、およびページあたりの結果数があります。メッセージ型を定義するために使用する .proto
ファイルを次に示します。
edition = "2023";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
ファイルの最初の行は、protobuf 言語仕様の edition 2023 を使用していることを指定します。
edition
(proto2/proto3 の場合はsyntax
)は、ファイルの最初の空でない、コメント以外の行である必要があります。edition
またはsyntax
が指定されていない場合、protocol buffer コンパイラは proto2 を使用していると見なします。
SearchRequest
メッセージ定義は、このタイプのメッセージに含めるデータごとに 3 つのフィールド(名前/値のペア)を指定します。各フィールドには、名前と型があります。
フィールド型の指定
前の例では、すべてのフィールドが スカラー型 です。2 つの整数 (page_number
と results_per_page
) と文字列 (query
) です。フィールドには、列挙型 や他のメッセージ型のような複合型も指定できます。
フィールド番号の割り当て
メッセージ定義の各フィールドには、1
から 536,870,911
までの番号を、次の制限付きで指定する必要があります。
- 指定された番号は、そのメッセージのすべてのフィールドの中で一意である必要があります。
- フィールド番号
19,000
~19,999
は、Protocol Buffers の実装用に予約されています。protocol buffer コンパイラは、メッセージでこれらの予約済みフィールド番号のいずれかを使用するとエラーを報告します。 - 以前に 予約済み のフィールド番号、または 拡張機能 に割り当てられているフィールド番号は使用できません。
この番号は、メッセージのワイヤ形式 でフィールドを識別するため、メッセージ型が使用された後は変更できません。フィールド番号を「変更」することは、そのフィールドを削除し、同じ型で新しい番号の新しいフィールドを作成することと同じです。適切な方法については、フィールドの削除 を参照してください。
フィールド番号は絶対に再利用しないでください。新しいフィールド定義で再利用するために、予約済み リストからフィールド番号を削除しないでください。フィールド番号の再利用による影響 を参照してください。
最も頻繁に設定されるフィールドには、1 ~ 15 のフィールド番号を使用する必要があります。フィールド番号の値が小さいほど、ワイヤ形式でのスペースが少なくなります。たとえば、1 ~ 15 の範囲のフィールド番号は、エンコードに 1 バイトかかります。16 ~ 2047 の範囲のフィールド番号は、2 バイトかかります。詳細については、Protocol Buffer エンコーディング を参照してください。
フィールド番号の再利用による影響
フィールド番号を再利用すると、ワイヤ形式のメッセージのデコードがあいまいになります。
protobuf ワイヤ形式は簡潔であり、ある定義を使用してエンコードされたフィールドを別の定義を使用してデコードする方法を提供しません。
ある定義を使用してフィールドをエンコードし、その同じフィールドを別の定義でデコードすると、次のことが起こる可能性があります。
- デバッグに費やす開発者の時間の損失
- パース/マージ エラー (最良のシナリオ)
- PII/SPII のリーク
- データの破損
フィールド番号の再利用の一般的な原因
フィールドの番号の振り直し (フィールドの番号順をより美しくするために行われる場合があります)。番号の振り直しは、事実上、番号の振り直しに関係するすべてのフィールドを削除して再度追加することになり、互換性のないワイヤ形式の変更が発生します。
フィールドを削除し、将来の再利用を防ぐために番号を 予約 しない。
フィールド番号は、フィールドのワイヤ形式を指定するために 3 ビットが使用されるため、32 ビットではなく 29 ビットに制限されています。詳細については、エンコーディングのトピック を参照してください。
フィールドのカーディナリティの指定
メッセージ フィールドは、次のいずれかになります。
単数:
単数フィールドには、明示的なカーディナリティ ラベルはありません。これには 2 つの状態が考えられます。
- フィールドが設定され、明示的に設定された値、またはワイヤからパースされた値が含まれています。これはワイヤにシリアライズされます。
- フィールドが設定されておらず、デフォルト値を返します。これはワイヤにシリアライズされません。
値が明示的に設定されたかどうかを確認できます。
エディションに移行された Proto3 暗黙的 フィールドは、
field_presence
機能セットをIMPLICIT
値で使用します。エディションに移行された Proto2
required
フィールドもfield_presence
機能を使用しますが、LEGACY_REQUIRED
に設定されます。repeated
: このフィールド型は、整形式メッセージで 0 回以上繰り返すことができます。繰り返される値の順序は保持されます。map
: これはペアのキー/値フィールド型です。このフィールド型の詳細については、マップ を参照してください。
繰り返されるフィールドはデフォルトでパックされます
proto エディションでは、スカラー数値型の repeated
フィールドは、デフォルトで packed
エンコーディングを使用します。
packed
エンコーディングの詳細については、Protocol Buffer エンコーディング を参照してください。
整形式メッセージ
protobuf メッセージに適用される場合、「整形式」という用語は、シリアライズ/デシリアライズされたバイトを指します。protoc パーサーは、指定された proto 定義ファイルがパース可能であることを検証します。
単数フィールドは、ワイヤ形式バイトに複数回出現する可能性があります。パーサーは入力を受け入れますが、そのフィールドの最後のインスタンスのみが生成されたバインディングを介してアクセス可能になります。詳細については、Last One Wins を参照してください。
メッセージ型の追加
複数のメッセージ型を 1 つの .proto
ファイルで定義できます。これは、複数の関連メッセージを定義する場合に役立ちます。たとえば、SearchResponse
メッセージ型に対応する応答メッセージ形式を定義する場合は、それを同じ .proto
に追加できます。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
...
}
メッセージの組み合わせは肥大化につながる 複数のメッセージ型(メッセージ、enum、サービスなど)を 1 つの .proto
ファイルで定義できますが、依存関係が異なる多数のメッセージが 1 つのファイルで定義されている場合、依存関係の肥大化につながる可能性もあります。.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 {
string query = 1;
// Which page number do we want?
int32 page_number = 2;
// Number of results to return per page.
int32 results_per_page = 3;
}
フィールドの削除
フィールドを削除すると、適切に行わないと重大な問題が発生する可能性があります。
フィールドが不要になり、クライアント コードからすべての参照が削除されたら、メッセージからフィールド定義を削除できます。ただし、必ず 削除されたフィールド番号を予約 してください。フィールド番号を予約しないと、開発者が将来その番号を再利用する可能性があります。
メッセージの JSON および TextFormat エンコーディングが引き続きパースできるように、フィールド名も予約する必要があります。
予約済みフィールド番号
フィールドを完全に削除したり、コメントアウトしたりしてメッセージ型を 更新 すると、将来の開発者は、型に独自の更新を加えるときにフィールド番号を再利用できます。これにより、フィールド番号の再利用による影響 で説明されているように、重大な問題が発生する可能性があります。これが起こらないようにするには、削除されたフィールド番号を reserved
リストに追加します。
protoc コンパイラは、将来の開発者がこれらの予約済みフィールド番号を使用しようとすると、エラー メッセージを生成します。
message Foo {
reserved 2, 15, 9 to 11;
}
予約済みフィールド番号範囲は包括的です(9 to 11
は 9, 10, 11
と同じです)。
予約済みフィールド名
古いフィールド名を後で再利用することは、TextProto または JSON エンコーディングを使用している場合を除き、一般的に安全です。TextProto または JSON エンコーディングでは、フィールド名がシリアライズされます。このリスクを回避するには、削除されたフィールド名を reserved
リストに追加できます。
予約済み名は、protoc コンパイラの動作のみに影響し、ランタイムの動作には影響しません。ただし、1 つの例外があります。TextProto 実装は、パース時に予約済み名を持つ不明なフィールドを(他の不明なフィールドのようにエラーを発生させることなく)破棄する場合があります(現在、C++ および Go 実装のみがこれを行います)。ランタイム JSON パースは、予約済み名の影響を受けません。
message Foo {
reserved 2, 15, 9 to 11;
reserved foo, bar;
}
同じ reserved
ステートメントでフィールド名とフィールド番号を混在させることはできないことに注意してください。
.proto
から何が生成されるか?
protocol buffer コンパイラ を .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 | int | 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 | int | 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 | str/unicode[5] | string | String (UTF-8) | string | string | String | ProtoString |
bytes | string | ByteString | str (Python 2)、bytes (Python 3) | []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] Python 文字列は、デコード時には unicode として表されますが、ASCII 文字列が指定されている場合は str にすることができます (これは変更される可能性があります)。
[6] 整数は 64 ビット マシンで使用され、文字列は 32 ビット マシンで使用されます。
これらの型をメッセージをシリアライズするときにどのようにエンコードするかについては、Protocol Buffer エンコーディング を参照してください。
フィールドのデフォルト値
メッセージがパースされるとき、エンコードされたメッセージ バイトに特定のフィールドが含まれていない場合、パースされたオブジェクト内のそのフィールドにアクセスすると、そのフィールドのデフォルト値が返されます。デフォルト値は型固有です。
- 文字列の場合、デフォルト値は空の文字列です。
- バイトの場合、デフォルト値は空のバイトです。
- bool の場合、デフォルト値は false です。
- 数値型の場合、デフォルト値はゼロです。
- メッセージ フィールドの場合、フィールドは設定されていません。その正確な値は言語に依存します。詳細については、生成されたコード ガイド を参照してください。
- enum の場合、デフォルト値は最初に定義された enum 値であり、0 である必要があります。Enum のデフォルト値 を参照してください。
繰り返されるフィールドのデフォルト値は空です(通常は適切な言語の空のリスト)。
マップ フィールドのデフォルト値は空です(通常は適切な言語の空のマップ)。
デフォルト スカラー値のオーバーライド
protobuf エディションでは、単数非メッセージ フィールドの明示的なデフォルト値を指定できます。たとえば、SearchRequest.result_per_page
フィールドにデフォルト値 10 を指定するとします。
int32 result_per_page = 3 [default = 10];
送信者が result_per_page
を指定しない場合、受信者は次の状態を観察します。
- result_per_page フィールドは存在しません。つまり、
has_result_per_page()
(hazzer メソッド) メソッドはfalse
を返します。 result_per_page
の値(「ゲッター」から返される)は10
です。
送信者が result_per_page
の値を送信した場合、デフォルト値 10 は無視され、送信者の値が「ゲッター」から返されます。
生成されたコードでのデフォルトの動作の詳細については、選択した言語の 生成されたコード ガイド を参照してください。
field_presence
機能が IMPLICIT
に設定されているフィールドには、明示的なデフォルト値を指定できません。
列挙型
メッセージ型を定義するときに、そのフィールドの 1 つが定義済みの値リストの 1 つだけを持つようにしたい場合があります。たとえば、各 SearchRequest
に 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 {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
Corpus corpus = 4;
}
Enum のデフォルト値
SearchRequest.corpus
フィールドのデフォルト値は CORPUS_UNSPECIFIED
です。これは、enum で定義された最初の値であるためです。
edition 2023 では、enum 定義で定義された最初の値は、必ず 値がゼロであり、ENUM_TYPE_NAME_UNSPECIFIED
または ENUM_TYPE_NAME_UNKNOWN
という名前を持つ必要があります。これは、次の理由によるものです。
- ゼロ値は、proto2 セマンティクスとの互換性のために最初の要素である必要があります。proto2 セマンティクスでは、最初の enum 値は、別の値が明示的に指定されない限り、デフォルトになります。
- ゼロ値は、proto3 セマンティクスとの互換性のためにも必要です。proto3 セマンティクスでは、ゼロ値は、この enum 型を使用するすべての暗黙的存在フィールドのデフォルト値として使用されます。
また、この最初のデフォルト値には、「この値は指定されていません」以外の意味を持たないことをお勧めします。
SearchRequest.corpus
フィールドのような enum フィールドのデフォルト値は、次のように明示的にオーバーライドできます。
Corpus corpus = 4 [default = CORPUS_UNIVERSAL];
enum 型が option features.enum_type = CLOSED;
を使用して proto2 から移行された場合、enum の最初の値に制限はありません。これらの型の enum の最初の値を変更することはお勧めしません。これは、明示的なフィールドのデフォルトなしにその enum 型を使用するフィールドのデフォルト値を変更するためです。
Enum 値のエイリアス
異なる enum 定数に同じ値を割り当てることで、エイリアスを定義できます。これを行うには、allow_alias
オプションを true
に設定する必要があります。そうしないと、エイリアスが見つかったときに protocol buffer コンパイラが警告メッセージを生成します。すべてのエイリアス値はシリアライズには有効ですが、デシリアライズ時には最初の値のみが使用されます。
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;
}
Enum 定数
列挙子定数は、32 ビット整数の範囲内である必要があります。enum
値はワイヤ上で varint エンコーディング を使用するため、負の値は非効率的であるため推奨されません。前の例のように、メッセージ定義内で enum
を定義することも、外部で定義することもできます。これらの enum
は、.proto
ファイルの任意のメッセージ定義で再利用できます。また、_MessageType_._EnumType_
構文を使用して、あるメッセージで宣言された enum
型を別のメッセージのフィールドの型として使用することもできます。
言語固有の Enum 実装
enum
を使用する .proto
で protocol buffer コンパイラを実行すると、生成されたコードには、Java、Kotlin、または C++ の場合は対応する enum
が含まれます。または、Python の場合は、ランタイムで生成されたクラスに整数値を持つシンボリック定数のセットを作成するために使用される特別な EnumDescriptor
クラスが含まれます。
重要
生成されたコードは、列挙子の数に関する言語固有の制限(1 つの言語で数千など)を受ける場合があります。使用する予定の言語の制限を確認してください。デシリアライズ中に、認識されない enum 値はメッセージに保持されますが、メッセージがデシリアライズされるときにどのように表現されるかは言語に依存します。C++ や Go など、指定されたシンボルの範囲外の値を持つオープン enum 型をサポートする言語では、不明な enum 値は、単にその基になる整数表現として格納されます。Java などのクローズド enum 型を持つ言語では、enum のケースを使用して認識されない値を表し、特別なアクセサーで基になる整数にアクセスできます。いずれの場合も、メッセージがシリアライズされると、認識されない値はメッセージとともにシリアライズされます。
重要
enum の動作と、さまざまな言語での現在の動作との対比については、Enum の動作 を参照してください。アプリケーションでメッセージ enum
を操作する方法の詳細については、選択した言語の 生成されたコード ガイド を参照してください。
予約値
enum エントリを完全に削除したり、コメントアウトしたりして enum 型を 更新 すると、将来のユーザーは、型に独自の更新を加えるときに数値を再利用できます。これにより、後で同じ .proto
の古いインスタンスをロードした場合、データの破損、プライバシー バグなど、重大な問題が発生する可能性があります。これが起こらないようにする方法の 1 つは、削除されたエントリの数値(および/または名前。JSON シリアライゼーションでも問題が発生する可能性があります)が reserved
であることを指定することです。protocol buffer コンパイラは、将来のユーザーがこれらの識別子を使用しようとするとエラーを報告します。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 {
string url = 1;
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
概念を使用してすべてのインポートを新しい場所に転送できます。
パブリック インポート機能は、protobuf 静的リフレクションを使用する C++ ターゲットだけでなく、Java、Kotlin、TypeScript、JavaScript、GCL でも使用できないことに注意してください。
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
フラグをプロジェクトのルートに設定し、すべてのインポートに完全修飾名を使用する必要があります。
proto2 および proto3 メッセージ型の使用
proto2 および proto3 メッセージ型をインポートし、それらを edition 2023 メッセージで使用したり、その逆も可能です。
ネストされた型
次の例のように、メッセージ型を他のメッセージ型内で定義して使用できます。ここでは、Result
メッセージは SearchResponse
メッセージ内で定義されています。
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
このメッセージ型を親メッセージ型の外部で再利用する場合は、_Parent_._Type_
として参照します。
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
メッセージは好きなだけ深くネストできます。以下の例では、Inner
という名前の 2 つのネストされた型は、異なるメッセージ内で定義されているため、完全に独立していることに注意してください。
message Outer { // Level 0
message MiddleAA { // Level 1
message Inner { // Level 2
int64 ival = 1;
bool booly = 2;
}
}
message MiddleBB { // Level 1
message Inner { // Level 2
int32 ival = 1;
bool booly = 2;
}
}
}
メッセージ型の更新
既存のメッセージ型がすべてのニーズを満たさなくなった場合(たとえば、メッセージ形式に追加のフィールドが必要になった場合など)、古い形式で作成されたコードを引き続き使用したい場合でも、心配する必要はありません。バイナリ ワイヤ形式を使用すると、既存のコードを壊すことなくメッセージ型を非常に簡単に更新できます。
注
JSON または proto テキスト形式 を使用して protocol buffer メッセージを保存する場合、proto 定義で行うことができる変更は異なります。Proto ベストプラクティス と次のルールを確認してください。
- 既存のフィールドのフィールド番号を変更しないでください。フィールド番号を「変更」することは、フィールドを削除し、同じ型の新しいフィールドを追加することと同じです。フィールドの番号を変更する場合は、フィールドの削除 の手順を参照してください。
- 新しいフィールドを追加した場合でも、以前のメッセージ形式を使用するコードによってシリアライズされたメッセージは、新しく生成されたコードで解析できます。新しいコードが古いコードによって生成されたメッセージと適切に相互作用できるように、これらの要素のデフォルト値を念頭に置いてください。同様に、新しいコードによって作成されたメッセージは古いコードで解析できます。古いバイナリは、解析時に新しいフィールドを単に無視します。詳細については、不明なフィールドのセクションを参照してください。
- フィールド番号が更新されたメッセージタイプで再利用されない限り、フィールドは削除できます。代わりにフィールドの名前を変更したり、おそらくプレフィックス「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
値はメッセージに保持されますが、メッセージがデシリアライズされるときにどのように表現されるかは言語に依存します。Intフィールドは常にその値を保持するだけです。- 単一の
optional
フィールドまたは拡張機能を新しいoneof
のメンバーに変更することはバイナリ互換性がありますが、一部の言語(特にGo)では、生成されたコードのAPIは互換性のない方法で変更されます。このため、GoogleはAIP-180で文書化されているように、パブリックAPIでそのような変更を行いません。ソース互換性に関する同じ注意点がありますが、複数のフィールドを新しいoneof
に移動することは、一度に複数のフィールドを設定するコードがないことを確認している場合は安全かもしれません。フィールドを既存のoneof
に移動することは安全ではありません。同様に、単一のフィールドoneof
をoptional
フィールドまたは拡張機能に変更することは安全です。 map<K, V>
と対応するrepeated
メッセージフィールドの間でフィールドを変更することはバイナリ互換性があります(メッセージレイアウトおよびその他の制限については、以下のマップを参照してください)。ただし、変更の安全性はアプリケーションに依存します。メッセージをデシリアライズおよび再シリアライズするとき、repeated
フィールド定義を使用するクライアントは意味的に同一の結果を生成します。ただし、map
フィールド定義を使用するクライアントは、エントリを並べ替えたり、重複するキーを持つエントリを削除したりする可能性があります。
不明なフィールド
不明なフィールドは、パーサーが認識しないフィールドを表す整形式のプロトコルバッファシリアライズされたデータです。たとえば、古いバイナリが新しいフィールドを持つ新しいバイナリによって送信されたデータを解析する場合、それらの新しいフィールドは古いバイナリで不明なフィールドになります。
Editionsメッセージは不明なフィールドを保持し、解析中およびシリアライズされた出力に含めます。これはproto2およびproto3の動作と一致します。
不明なフィールドの保持
一部のアクションは、不明なフィールドが失われる原因となる可能性があります。たとえば、次のいずれかを行うと、不明なフィールドが失われます
- プロトを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
},
// Ensures all field numbers in this extension range are declarations.
verification = DECLARATION
];
}
UserContent
は、フィールド番号126
が完全修飾名.kittens.kitten_videos
と完全修飾型.kittens.Video
を持つrepeated
拡張フィールドによって使用されることを宣言します。拡張機能の宣言の詳細については、拡張機能の宣言を参照してください。
コンテナメッセージのファイル(media/user_content.proto
)は、kitten_video拡張機能の定義(kittens/video_ext.proto
)をインポートしないことに注意してください。
拡張フィールドのワイヤ形式エンコーディングは、同じフィールド番号、型、およびカーディナリティを持つ標準フィールドと比較して違いはありません。したがって、フィールド番号、型、およびカーディナリティが一定である限り、標準フィールドをコンテナから拡張機能として移動したり、拡張フィールドをコンテナメッセージに標準フィールドとして移動したりすることは安全です。
ただし、拡張機能はコンテナメッセージの外部で定義されているため、特定の拡張フィールドを取得および設定するための特殊なアクセサは生成されません。例として、protobufコンパイラはAddKittenVideos()
またはGetKittenVideos()
アクセサを生成しません。代わりに、拡張機能は、HasExtension()
、ClearExtension()
、GetExtension()
、MutableExtension()
、およびAddExtension()
のようなパラメータ化された関数を介してアクセスされます。
C++では、次のようになります
UserContent user_content;
user_content.AddExtension(kittens::kitten_videos, new kittens::Video());
assert(1 == user_content.GetExtensionCount(kittens::kitten_videos));
user_content.GetExtension(kittens::kitten_videos, 0);
拡張範囲の定義
コンテナメッセージの所有者である場合は、メッセージへの拡張機能の拡張範囲を定義する必要があります。
拡張フィールドに割り当てられたフィールド番号は、標準フィールドに再利用できません。
拡張範囲は、定義後に拡張しても安全です。適切なデフォルトは、1000個の比較的小さな数値を割り当て、拡張機能の宣言を使用してそのスペースを密に埋めることです
message ModernExtendableMessage {
// All extensions in this range should use extension declarations.
extensions 1000 to 2000 [verification = DECLARATION];
}
実際の拡張機能の前に拡張機能宣言の範囲を追加する場合は、この新しい範囲に宣言が使用されるように強制するためにverification = DECLARATION
を追加する必要があります。このプレースホルダーは、実際の宣言が追加されると削除できます。
既存の拡張範囲を同じ合計範囲をカバーする別々の範囲に分割しても安全です。これは、レガシーメッセージタイプを拡張機能の宣言に移行するために必要な場合があります。たとえば、移行前は、範囲は次のように定義されている場合があります
message LegacyMessage {
extensions 1000 to max;
}
移行後(範囲の分割)は、次のようになります
message LegacyMessage {
// Legacy range that was using an unverified allocation scheme.
extensions 1000 to 524999999 [verification = UNVERIFIED];
// Current range that uses extension declarations.
extensions 525000000 to max [verification = DECLARATION];
}
拡張範囲を移動または縮小するために、開始フィールド番号を増やしたり、終了フィールド番号を減らしたりすることは安全ではありません。これらの変更は、既存の拡張機能を無効にする可能性があります。
プロトのほとんどのインスタンスで移入される標準フィールドには、フィールド番号1〜15を使用することをお勧めします。これらの番号を拡張機能に使用することはお勧めしません。
番号付け規則に非常に大きなフィールド番号を持つ拡張機能が含まれる可能性がある場合は、拡張範囲がmax
キーワードを使用して可能な最大のフィールド番号まで拡張されるように指定できます
message Foo {
extensions 1000 to max;
}
max
は229 - 1、つまり536,870,911です。
拡張番号の選択
拡張機能は、コンテナメッセージの外部で指定できるフィールドにすぎません。フィールド番号の割り当てに関するすべての同じルールが、拡張フィールド番号に適用されます。フィールド番号の再利用の結果も、拡張フィールド番号の再利用に適用されます。
コンテナメッセージが拡張機能の宣言を使用している場合、一意の拡張フィールド番号を選択するのは簡単です。新しい拡張機能を定義するときは、コンテナメッセージで定義された最大の拡張範囲からの他のすべての宣言よりも上の最小のフィールド番号を選択します。たとえば、コンテナメッセージが次のように定義されている場合
message Container {
// Legacy range that was using an unverified allocation scheme
extensions 1000 to 524999999;
// Current range that uses extension declarations. (highest extension range)
extensions 525000000 to max [
declaration = {
number: 525000001,
full_name: ".bar.baz_ext",
type: ".bar.Baz"
}
// 525,000,002 is the lowest field number above all other declarations
];
}
Container
の次の拡張機能は、番号525000002
で新しい宣言を追加する必要があります。
未検証の拡張機能番号割り当て(推奨されません)
コンテナメッセージの所有者は、拡張機能の宣言を放棄して、独自の未検証の拡張機能番号割り当て戦略を支持することを選択できます。
未検証の割り当てスキームは、選択された拡張範囲内で拡張フィールド番号を割り当てるために、protobufエコシステムの外部にあるメカニズムを使用します。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 {
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 {
Photo puppy_photo = 127;
}
...
}
ただし、メッセージ型を持つ拡張機能をその型内で定義する必要はありません。標準定義パターンも使用できます
import "media/user_content.proto";
package puppies;
message Photo {
...
}
// This can even be in a different file.
extend media.UserContent {
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
多くのsingularフィールドがあり、一度に最大1つのフィールドが設定されるメッセージがある場合は、oneof機能を使用することでこの動作を強制し、メモリを節約できます。
Oneofフィールドはsingularフィールドに似ていますが、oneof内のすべてのフィールドがメモリを共有し、一度に最大1つのフィールドを設定できます。oneofのメンバーを設定すると、他のすべてのメンバーが自動的にクリアされます。選択した言語に応じて、特別なcase()
またはWhichOneof()
メソッドを使用して、oneofでどの値が設定されているか(ある場合)を確認できます。
複数の値が設定されている場合、プロトの順序によって決定される最後に設定された値は、以前のすべての値を上書きすることに注意してください。
oneofフィールドのフィールド番号は、囲みメッセージ内で一意である必要があります。
Oneof の使用
.proto
でoneofを定義するには、oneof
キーワードの後にoneof名を続けます。この場合はtest_oneof
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
次に、oneofフィールドをoneof定義に追加します。map
フィールドとrepeated
フィールドを除く、任意の型のフィールドを追加できます。repeatedフィールドをoneofに追加する必要がある場合は、repeatedフィールドを含むメッセージを使用できます。
生成されたコードでは、oneofフィールドは通常のフィールドと同じゲッターとセッターを持っています。また、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のメンバーであるかどうかを知る方法がないため、違いを区別する方法はありません。
タグの再利用の問題
- singularフィールドをoneofの内外に移動する:メッセージがシリアライズおよび解析された後、情報の一部(一部のフィールドはクリアされます)が失われる可能性があります。ただし、単一のフィールドを新しいoneofに安全に移動でき、1つだけが設定されていることがわかっている場合は、複数のフィールドを移動できる場合があります。詳細については、メッセージタイプの更新を参照してください。
- oneofフィールドを削除して再度追加する:これにより、メッセージがシリアライズおよび解析された後、現在設定されているoneofフィールドがクリアされる可能性があります。
- oneofを分割またはマージする:これは、singularフィールドの移動と同様の問題があります。
マップ
データ定義の一部として連想マップを作成する場合は、プロトコルバッファは便利なショートカット構文を提供します
map<key_type, value_type> map_field = N;
…ここで、key_type
は任意の整数型または文字列型(したがって、浮動小数点型とbytes
を除く任意のスカラー型)にすることができます。enumもprotoメッセージもkey_type
には有効でないことに注意してください。value_type
は、別のマップを除く任意の型にすることができます。
したがって、たとえば、各Project
メッセージが文字列キーに関連付けられているプロジェクトのマップを作成する場合は、次のように定義できます
map<string, Project> projects = 3;
マップ機能
- 拡張機能はマップではサポートされていません。
- マップフィールドを
repeated
にすることはできません。 - マップ値のワイヤ形式の順序とマップ反復の順序は未定義であるため、マップアイテムが特定の順序になっていることを信頼することはできません。
.proto
のテキスト形式を生成する場合、マップはキーでソートされます。数値キーは数値的にソートされます。- ワイヤから解析する場合、またはマージする場合、重複するマップキーがある場合は、最後に検出されたキーが使用されます。テキスト形式からマップを解析する場合、重複するキーがあると解析が失敗する可能性があります。
- マップフィールドにキーを提供したが値を提供しない場合、フィールドがシリアライズされるときの動作は言語に依存します。C++、Java、Kotlin、およびPythonでは、型のデフォルト値がシリアライズされますが、他の言語では何もシリアライズされません。
- マップ
foo
と同じスコープにシンボルFooEntry
が存在することはできません。FooEntry
はマップの実装ですでに使用されているためです。
生成されたマップAPIは現在、サポートされているすべての言語で利用可能です。選択した言語のマップAPIの詳細については、関連するAPIリファレンスを参照してください。
下位互換性
マップ構文はワイヤ上で次と同等であるため、マップをサポートしていないプロトコルバッファ実装でもデータを処理できます
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
マップをサポートするプロトコルバッファ実装は、以前の定義で受け入れられるデータを生成および受け入れる必要があります。
パッケージ
オプションのpackage
指定子を.proto
ファイルに追加して、プロトコルメッセージタイプ間の名前の衝突を防ぐことができます。
package foo.bar;
message Open { ... }
次に、メッセージタイプのフィールドを定義するときにパッケージ指定子を使用できます
message Foo {
...
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
にあります。
たとえばPythonのように、package
ディレクティブが生成されたコードに直接影響を与えない場合でも、.proto
ファイルのパッケージを指定することを強くお勧めします。そうしないと、記述子で名前の衝突が発生し、プロトが他の言語で移植できなくなる可能性があるためです。
パッケージと名前解決
プロトコルバッファ言語の型名解決はC++のように機能します。最初に最も内側のスコープが検索され、次に次の内側のスコープが検索され、各パッケージは親パッケージに対して「内側」と見なされます。先頭の「.」(たとえば、.foo.bar.Baz
)は、代わりに最も外側のスコープから開始することを意味します。
プロトコルバッファコンパイラは、インポートされた.proto
ファイルを解析することにより、すべての型名を解決します。各言語のコードジェネレーターは、スコープ規則が異なっていても、その言語で各タイプを参照する方法を知っています。
サービスの定義
メッセージタイプをRPC(リモートプロシージャコール)システムで使用する場合は、.proto
ファイルでRPCサービスインターフェイスを定義できます。プロトコルバッファコンパイラは、選択した言語でサービスインターフェイスコードとスタブを生成します。たとえば、SearchRequest
を受け取り、SearchResponse
を返すメソッドを持つRPCサービスを定義する場合は、.proto
ファイルで次のように定義できます
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
プロトコルバッファで使用する最も簡単なRPCシステムは、gRPCです。Googleで開発された言語およびプラットフォームニュートラルなオープンソースRPCシステムです。gRPCはプロトコルバッファとうまく連携し、特別なプロトコルバッファコンパイラプラグインを使用して.proto
ファイルから直接関連するRPCコードを生成できます。
gRPCを使用しない場合は、独自のRPC実装でプロトコルバッファを使用することもできます。詳細については、Proto2言語ガイドを参照してください。
プロトコルバッファのRPC実装を開発するための進行中のサードパーティプロジェクトも多数あります。私たちが知っているプロジェクトへのリンクのリストについては、サードパーティアドオンwikiページを参照してください。
JSON マッピング
標準のprotobufバイナリワイヤ形式は、protobufを使用する2つのシステム間の通信に推奨されるシリアライズ形式です。protobufワイヤ形式ではなくJSONを使用するシステムとの通信の場合、ProtobufはProtoJSONでの正規エンコーディングをサポートしています。
オプション
.proto
ファイルの個々の宣言は、多数のオプションで注釈を付けることができます。オプションは宣言の全体的な意味を変更しませんが、特定のコンテキストでの処理方法に影響を与える可能性があります。使用可能なオプションの完全なリストは、/google/protobuf/descriptor.proto
で定義されています。
一部のオプションはファイルレベルオプションであり、メッセージ、enum、またはサービス定義の内側ではなく、トップレベルスコープで記述する必要があります。一部のオプションはメッセージレベルオプションであり、メッセージ定義の内側に記述する必要があります。一部のオプションはフィールドレベルオプションであり、フィールド定義の内側に記述する必要があります。オプションは、enum型、enum値、oneofフィールド、サービス型、およびサービスメソッドにも記述できます。ただし、現在、これらのいずれにも有用なオプションは存在しません。
最も一般的に使用されるオプションを次に示します
java_package
(ファイルオプション):生成されたJava/Kotlinクラスに使用するパッケージ。.proto
ファイルで明示的なjava_package
オプションが指定されていない場合、デフォルトではプロトパッケージ(.proto
ファイルで「package」キーワードを使用して指定)が使用されます。ただし、プロトパッケージは通常、リバースドメイン名で始まることが期待されていないため、適切な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
になるBooleanオプションです。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生成クラスおよびenumにプレフィックスが付加されるObjective-Cクラスプレフィックスを設定します。デフォルトはありません。Appleが推奨するように、3〜5文字の大文字のプレフィックスを使用する必要があります。2文字のプレフィックスはすべてAppleによって予約されていることに注意してください。packed
(フィールドオプション):protobufエディションでは、このオプションはtrue
にロックされています。アンパックされたワイヤ形式を使用するには、エディション機能を使用してこのオプションをオーバーライドできます。これは、次の例に示すように、バージョン2.3.0より前のパーサーとの互換性を提供します(まれに必要な場合)repeated int32 samples = 4 [features.repeated_field_encoding = EXPANDED];
deprecated
(フィールドオプション):true
に設定すると、フィールドが非推奨であり、新しいコードで使用すべきではないことを示します。ほとんどの言語では、これは実際には効果がありません。Javaでは、これは@Deprecated
アノテーションになります。C++の場合、clang-tidyは非推奨フィールドが使用されるたびに警告を生成します。将来的には、他の言語固有のコードジェネレーターがフィールドのアクセサに非推奨アノテーションを生成する可能性があり、これにより、フィールドを使用しようとするコードをコンパイルするときに警告が発行されます。フィールドが誰にも使用されておらず、新しいユーザーが使用するのを防ぎたい場合は、フィールド宣言をreservedステートメントに置き換えることを検討してください。int32 old_field = 6 [deprecated = true];
Enum 値オプション
Enum値オプションがサポートされています。deprecated
オプションを使用して、値がもう使用すべきではないことを示すことができます。拡張機能を使用してカスタムオプションを作成することもできます。
次の例は、これらのオプションを追加するための構文を示しています
import "google/protobuf/descriptor.proto";
extend google.protobuf.EnumValueOptions {
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値とフィールドにカスタムオプションを適用する方法については、カスタムオプションを参照してください。
カスタム オプション
プロトコルバッファでは、独自のオプションを定義して使用することもできます。これは、ほとんどの人が必要としない高度な機能であることに注意してください。独自のオプションを作成する必要があると思われる場合は、詳細についてProto2言語ガイドを参照してください。カスタムオプションの作成には拡張機能を使用することに注意してください。
オプションの保持
オプションには、オプションが生成されたコードに保持されるかどうかを制御する保持の概念があります。オプションはデフォルトでランタイム保持を持ちます。つまり、オプションは生成されたコードに保持され、生成された記述子プールでランタイムに表示されます。ただし、retention = RETENTION_SOURCE
を設定して、オプション(またはオプション内のフィールド)をランタイムで保持しないように指定できます。これはソース保持と呼ばれます。
オプションの保持は高度な機能であり、ほとんどのユーザーは気にする必要はありませんが、バイナリにオプションを保持するためのコードサイズコストをかけずに特定のオプションを使用したい場合には役立ちます。ソース保持を持つオプションは、protoc
および protoc
プラグインからは引き続き可視であるため、コードジェネレーターはそれらを使用して動作をカスタマイズできます。
保持は、オプションに直接設定できます。例えば、このようにです。
extend google.protobuf.FileOptions {
int32 source_retention_option = 1234
[retention = RETENTION_SOURCE];
}
また、プレーンフィールドに設定することもできます。その場合、そのフィールドがオプション内に現れる場合にのみ有効になります。
message OptionsMessage {
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 はこれを強制し、ターゲット制約に違反がある場合はエラーを発生させます。
一見すると、この機能は不要に思えるかもしれません。なぜなら、すべてのカスタムオプションは特定のエンティティの options メッセージの拡張であり、すでにオプションをその1つのエンティティに制約しているからです。しかし、オプションターゲットは、複数のエンティティタイプに適用される共有のオプションメッセージがあり、そのメッセージ内の個々のフィールドの使用を制御したい場合に役立ちます。例えば
message MyOptions {
string file_only_option = 1 [targets = TARGET_TYPE_FILE];
int32 message_and_enum_option = 2 [targets = TARGET_TYPE_MESSAGE,
targets = TARGET_TYPE_ENUM];
}
extend google.protobuf.FileOptions {
MyOptions file_options = 50000;
}
extend google.protobuf.MessageOptions {
MyOptions message_options = 50000;
}
extend google.protobuf.EnumOptions {
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 ファイルに対して protocol buffer コンパイラ protoc
を実行する必要があります。コンパイラをインストールしていない場合は、パッケージをダウンロード し、README の指示に従ってください。Go の場合は、コンパイラ用の特別なコードジェネレータープラグインもインストールする必要があります。これとインストール手順は、GitHub の golang/protobuf リポジトリにあります。
Protocol Compiler は次のように呼び出されます。
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 以外のコードが同じ proto を使用する場合、パスプレフィックスは意味をなさなくなります。したがって、一般的には、//myteam/mypackage
のような関連する言語に依存しないディレクトリに proto を配置してください。
この規則の例外は、proto がテストなど、Java コンテキストでのみ使用されることが明らかな場合です。
サポートされているプラットフォーム
に関する情報
- サポートされているオペレーティングシステム、コンパイラ、ビルドシステム、および C++ バージョンについては、Foundational C++ Support Policy を参照してください。
- サポートされている PHP バージョンについては、Supported PHP versions を参照してください。