言語ガイド (proto 3)
このガイドでは、プロトコルバッファ言語を使用してプロトコルバッファデータを構造化する方法について説明します。これには、.proto
ファイルの構文、および .proto
ファイルからデータアクセス クラスを生成する方法が含まれます。プロトコルバッファ言語の proto3 リビジョンを対象としています。
エディションの構文については、Protobuf エディション言語ガイドを参照してください。
proto2 の構文については、Proto2 言語ガイドを参照してください。
これはリファレンス ガイドです。このドキュメントで説明されている多くの機能を使用するステップバイステップの例については、選択した言語のチュートリアルを参照してください。
メッセージ型の定義
まず、非常に単純な例を見てみましょう。検索リクエストのメッセージ形式を定義するとします。各検索リクエストには、クエリ文字列、関心のある結果の特定のページ、およびページあたりの結果数が含まれます。メッセージ型を定義するために使用する .proto
ファイルを次に示します。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
ファイルの最初の行は、protobuf 言語仕様の proto3 リビジョンを使用していることを指定します。
edition
(または proto2/proto3 の場合はsyntax
)は、ファイルの最初の空でない、コメントでない行である必要があります。edition
またはsyntax
が指定されていない場合、プロトコルバッファ コンパイラは、proto2 を使用していると仮定します。
SearchRequest
メッセージ定義では、この種のメッセージに含める各データに対して、3つのフィールド(名前/値のペア)を指定します。各フィールドには名前と型があります。
フィールド型の指定
前の例では、すべてのフィールドがスカラ型です。2つの整数(page_number
とresults_per_page
)と文字列(query
)です。フィールドには、列挙型や、他のメッセージ型のような複合型も指定できます。
フィールド番号の割り当て
メッセージ定義の各フィールドには、1
から536,870,911
までの数値を指定する必要があります。ただし、以下の制限があります。
- 指定された番号は、そのメッセージのすべてのフィールド間で一意である必要があります。
- フィールド番号
19,000
から19,999
は Protocol Buffers の実装用に予約されています。メッセージ内でこれらの予約済みフィールド番号のいずれかを使用すると、プロトコルバッファ コンパイラは警告を発します。 - 以前に予約されたフィールド番号や、拡張機能に割り当てられたフィールド番号は使用できません。
この番号は、メッセージのワイヤ形式でフィールドを識別するため、メッセージ型が使用され始めると変更できません。フィールド番号を「変更する」ことは、そのフィールドを削除し、同じ型で新しい番号を持つ新しいフィールドを作成することと同じです。この適切な方法については、フィールドの削除を参照してください。
フィールド番号は決して再利用しないでください。予約済みリストからフィールド番号を取り出して、新しいフィールド定義で再利用することは避けてください。フィールド番号の再利用による影響を参照してください。
頻繁に設定されるフィールドには、フィールド番号1から15を使用する必要があります。フィールド番号の値が小さいほど、ワイヤ形式でのスペースが少なくて済みます。たとえば、1から15の範囲のフィールド番号はエンコードに1バイトかかります。16から2047の範囲のフィールド番号は2バイトかかります。これについては、Protocol Buffer エンコーディングで詳しく知ることができます。
フィールド番号の再利用による影響
フィールド番号を再利用すると、ワイヤ形式メッセージのデコードがあいまいになります。
protobuf のワイヤ形式は軽量であり、ある定義を使用してエンコードされ、別の定義を使用してデコードされたフィールドを検出する方法を提供しません。
ある定義を使用してフィールドをエンコードし、その後、別の定義でその同じフィールドをデコードすると、次の問題が発生する可能性があります。
- デバッグによる開発時間の損失
- パース/マージ エラー(最良のシナリオ)
- PII/SPII の漏洩
- データ破損
フィールド番号再利用の一般的な原因
- フィールドの再番号付け(フィールドの番号順をより美しくするために行われることがあります)。再番号付けは、再番号付けに関与するすべてのフィールドを実質的に削除し、再追加することになり、互換性のないワイヤ形式の変更を引き起こします。
- フィールドを削除し、将来の再利用を防ぐために番号を予約しないこと。
フィールド番号は32ビットではなく29ビットに制限されています。これは、フィールドのワイヤ形式を指定するために3ビットが使用されるためです。詳細については、エンコーディングのトピックを参照してください。
フィールドの基数指定
メッセージフィールドは以下のいずれかです。
単一:
proto3 では、単一フィールドには2つの種類があります。
optional
: (推奨)optional
フィールドには、2つの可能な状態のいずれかがあります。- フィールドが設定されており、明示的に設定された値またはワイヤからパースされた値を含んでいます。これはワイヤにシリアル化されます。
- フィールドが設定されておらず、デフォルト値を返します。これはワイヤにシリアル化されません。
値が明示的に設定されたかどうかを確認できます。
optional
は、protobuf エディションおよび proto2 との最大限の互換性のために、暗黙的フィールドよりも推奨されます。implicit: (非推奨)暗黙的フィールドには明示的な基数ラベルがなく、以下のように動作します。
フィールドがメッセージ型の場合、
optional
フィールドとまったく同じように動作します。フィールドがメッセージでない場合、2つの状態があります。
- フィールドは、明示的に設定された、またはワイヤからパースされた非デフォルト(非ゼロ)値に設定されています。これはワイヤにシリアル化されます。
- フィールドはデフォルト(ゼロ)値に設定されています。これはワイヤにシリアル化されません。実際、デフォルト(ゼロ)値がワイヤから設定またはパースされたのか、まったく提供されなかったのかを判断することはできません。この主題の詳細については、フィールドの有無を参照してください。
repeated
: このフィールド型は、整形式のメッセージ内で0回以上繰り返すことができます。繰り返された値の順序は保持されます。map
: これはペアになったキー/値のフィールド型です。このフィールド型の詳細については、マップを参照してください。
繰り返しフィールドはデフォルトでパックされる
proto3 では、スカラ数値型の repeated
フィールドは、デフォルトで packed
エンコーディングを使用します。
packed
エンコーディングの詳細については、Protocol Buffer エンコーディングを参照してください。
メッセージ型フィールドには常にフィールドの有無が存在する
proto3 では、メッセージ型フィールドには既にフィールドの有無が存在します。そのため、optional
修飾子を追加しても、そのフィールドのフィールドの有無は変わりません。
以下のコードサンプルにおける Message2
と Message3
の定義は、すべての言語で同じコードを生成し、バイナリ、JSON、TextFormat における表現に違いはありません。
syntax="proto3";
package foo.bar;
message Message1 {}
message Message2 {
Message1 foo = 1;
}
message Message3 {
optional Message1 bar = 1;
}
整形式メッセージ
protobuf メッセージに適用される「整形式」という用語は、シリアル化/デシリアル化されたバイトを参照します。protoc パーサーは、指定されたプロト定義ファイルがパース可能であることを検証します。
単一フィールドは、ワイヤ形式バイト内に複数回出現する可能性があります。パーサーは入力を受け入れますが、生成されたバインディングを介してアクセスできるのは、そのフィールドの最後のインスタンスのみです。このトピックの詳細については、最後に設定されたものが優先されるを参照してください。
メッセージ型の追加
複数のメッセージ型を単一の .proto
ファイル内で定義できます。これは、関連する複数のメッセージを定義する場合に便利です。たとえば、SearchResponse
メッセージ型に対応する返信メッセージ形式を定義したい場合、同じ .proto
に追加できます。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 results_per_page = 3;
}
message SearchResponse {
...
}
メッセージの結合は肥大化につながる 複数のメッセージ型(メッセージ、enum、サービスなど)を単一の .proto
ファイルで定義することは可能ですが、依存関係が異なる多数のメッセージが単一ファイルで定義されている場合、依存関係の肥大化にもつながる可能性があります。.proto
ファイルごとにできるだけ少ないメッセージ型を含めることをお勧めします。
コメントの追加
.proto
ファイルにコメントを追加するには
.proto
コード要素の前の行には、C/C++/Java の行末スタイルコメント「//」を使用することを推奨します。C スタイルのインライン/複数行コメント
/* ... */
も受け入れられます。- 複数行コメントを使用する場合は、「*」のマージン行を使用することが推奨されます。
/**
* SearchRequest represents a search query, with pagination options to
* indicate which results to include in the response.
*/
message SearchRequest {
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 から 11
は 9, 10, 11
と同じです)。
予約済みフィールド名
古いフィールド名を後で再利用することは一般的に安全ですが、フィールド名がシリアル化される TextProto または JSON エンコーディングを使用する場合は例外です。このリスクを避けるために、削除したフィールド名を reserved
リストに追加できます。
予約名は protoc コンパイラの動作のみに影響し、ランタイムの動作には影響しません。ただし、1つの例外があります。TextProto の実装は、パース時に予約名を持つ不明なフィールドを破棄する場合があります(他の不明なフィールドのようにエラーを発生させずに)。現在、C++ と Go の実装のみがこれを行います。ランタイムの JSON パースは予約名に影響されません。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
同じ reserved
ステートメント内でフィールド名とフィールド番号を混在させることはできません。
.proto
から何が生成されるか?
.proto
ファイルでプロトコルバッファ コンパイラを実行すると、コンパイラは、ファイルで記述したメッセージ型を操作するために必要なコードを、選択した言語で生成します。これには、フィールド値の取得と設定、メッセージの出力ストリームへのシリアル化、入力ストリームからのメッセージのパースが含まれます。
- C++ の場合、コンパイラは各
.proto
ファイルから.h
および.cc
ファイルを生成し、ファイルに記述された各メッセージ型に対応するクラスを作成します。 - Java の場合、コンパイラは各メッセージ型に対応するクラスを持つ
.java
ファイルと、メッセージクラス インスタンスを作成するための特別なBuilder
クラスを生成します。 - Kotlin の場合、Java で生成されたコードに加えて、コンパイラは各メッセージ型に対して改善された Kotlin API を持つ
.kt
ファイルを生成します。これには、メッセージ インスタンスの作成を簡素化する DSL、null 許容フィールド アクセサ、およびコピー関数が含まれます。 - 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 | 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 | std::string | String | str/unicode[5] | string | String (UTF-8) | string | string | String | ProtoString |
bytes | std::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ビットマシンでは Integer が使用され、32ビットマシンでは string が使用されます。
メッセージをシリアル化する際にこれらの型がどのようにエンコードされるかについては、Protocol Buffer エンコーディングで詳しく知ることができます。
フィールドのデフォルト値
メッセージがパースされたとき、エンコードされたメッセージバイトに特定のフィールドが含まれていない場合、パースされたオブジェクトでそのフィールドにアクセスすると、そのフィールドのデフォルト値が返されます。デフォルト値は型固有です。
- 文字列の場合、デフォルト値は空の文字列です。
- バイトの場合、デフォルト値は空のバイトです。
- ブール値の場合、デフォルト値は false です。
- 数値型の場合、デフォルト値はゼロです。
- メッセージフィールドの場合、フィールドは設定されていません。その正確な値は言語に依存します。詳細については、生成コード ガイドを参照してください。
- enum の場合、デフォルト値は最初に定義された enum 値であり、これは0でなければなりません。Enum のデフォルト値を参照してください。
繰り返しフィールドのデフォルト値は空です(通常、適切な言語では空のリストです)。
マップフィールドのデフォルト値は空です(通常、適切な言語では空のマップです)。
暗黙的な存在のスカラ フィールドの場合、メッセージがパースされると、そのフィールドが明示的にデフォルト値に設定されたのか(例えば、ブール値が false
に設定されたのか)、まったく設定されなかったのかを区別する方法がないことに注意してください。メッセージ型を定義する際にはこの点を考慮する必要があります。たとえば、デフォルトでもその動作が発生してほしくない場合は、false
に設定すると何らかの動作がオンになるようなブール値を使用しないでください。また、スカラ メッセージ フィールドがデフォルトに設定されている場合、その値はワイヤにシリアル化されないことにも注意してください。float または double の値が +0 に設定されている場合、シリアル化されませんが、-0 は区別され、シリアル化されます。
生成コードでデフォルトがどのように機能するかについての詳細は、選択した言語の生成コード ガイドを参照してください。
列挙型
メッセージ型を定義する際、そのフィールドの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 で定義された最初の値であるためです。
proto3 では、enum 定義で最初に定義された値は、値がゼロである必要があり、名前は ENUM_TYPE_NAME_UNSPECIFIED
または ENUM_TYPE_NAME_UNKNOWN
であるべきです。これは、次の理由によります。
- 数値のデフォルト値として0を使用できるように、ゼロ値がなければなりません。
- 明示的に異なる値が指定されない限り、最初の enum 値がデフォルトとなるproto2 のセマンティクスとの互換性のために、ゼロ値は最初の要素である必要があります。
また、この最初のデフォルト値は、「この値は未指定であった」以外の意味を持たないことを推奨します。
Enum 値のエイリアス
異なる enum 定数に同じ値を割り当てることでエイリアスを定義できます。これを行うには、allow_alias
オプションを true
に設定する必要があります。そうしないと、エイリアスが見つかったときにプロトコルバッファ コンパイラが警告メッセージを生成します。すべてのエイリアス値はシリアル化には有効ですが、デシリアル化時には最初の値のみが使用されます。
enum EnumAllowingAlias {
option allow_alias = true;
EAA_UNSPECIFIED = 0;
EAA_STARTED = 1;
EAA_RUNNING = 1;
EAA_FINISHED = 2;
}
enum EnumNotAllowingAlias {
ENAA_UNSPECIFIED = 0;
ENAA_STARTED = 1;
// ENAA_RUNNING = 1; // Uncommenting this line will cause a warning message.
ENAA_FINISHED = 2;
}
列挙子定数は32ビット整数の範囲内になければなりません。enum
値はワイヤ上でvarint エンコーディングを使用するため、負の値は非効率的であり、推奨されません。enum
は、前の例のようにメッセージ定義内で定義することも、外部で定義することもできます。これらの enum
は、.proto
ファイル内の任意のメッセージ定義で再利用できます。また、あるメッセージで宣言された enum
型を、_MessageType_._EnumType_
という構文を使用して、別のメッセージのフィールドの型として使用することもできます。
enum
を使用する .proto
でプロトコルバッファ コンパイラを実行すると、生成されたコードには、Java、Kotlin、または C++ 用の対応する enum
、またはランタイム生成クラスで整数値を持つシンボリック定数のセットを作成するために使用される Python 用の特別な EnumDescriptor
クラスが含まれます。
重要
生成されたコードは、列挙子の数に関して言語固有の制限を受ける場合があります(ある言語では数千程度)。使用を計画している言語の制限を確認してください。デシリアル化中、認識されない enum 値はメッセージ内に保持されますが、メッセージがデシリアル化されたときにこれがどのように表現されるかは言語に依存します。C++ や Go のように、指定されたシンボルの範囲外の値を持つオープン enum 型をサポートする言語では、不明な enum 値は単にその基になる整数表現として格納されます。Java のようにクローズド enum 型を持つ言語では、enum 内のケースが認識されない値を表現するために使用され、基になる整数は特別なアクセサでアクセスできます。いずれの場合も、メッセージがシリアル化される場合、認識されない値はメッセージとともにシリアル化されます。
重要
enum がどのように機能すべきか、そして現在さまざまな言語でどのように機能しているかについては、Enum の動作を参照してください。アプリケーションでメッセージ enum
を操作する方法の詳細については、選択した言語の生成コード ガイドを参照してください。
予約値
enum エントリを完全に削除したりコメントアウトしたりして enum 型を更新すると、将来のユーザーがその型を更新する際に、その数値値を再利用する可能性があります。これは、後で同じ .proto
の古いインスタンスをロードした場合に、データ破損、プライバシーバグなどの深刻な問題を引き起こす可能性があります。これを確実に防ぐ1つの方法は、削除されたエントリの数値(および/または名前。これは JSON シリアル化でも問題を引き起こす可能性があります)を reserved
として指定することです。将来のユーザーがこれらの識別子を使用しようとすると、プロトコルバッファ コンパイラは警告を発します。予約済み数値範囲が最大可能値までであることを max
キーワードを使用して指定できます。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
同じ reserved
ステートメント内でフィールド名と数値を混在させることはできません。
他のメッセージ型の使用
他のメッセージ型をフィールド型として使用できます。たとえば、各 SearchResponse
メッセージに Result
メッセージを含めたいとします。これを行うには、同じ .proto
ファイルで Result
メッセージ型を定義し、その後 SearchResponse
で Result
型のフィールドを指定します。
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
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 で利用可能なパブリックインポート機能は、.proto
ファイル全体を移動する場合、または java_multiple_files = true
を使用する場合に最も効果的です。これらの場合、生成された名前は安定しており、コード内の参照を更新する必要がありません。java_multiple_files = true
なしで .proto
ファイルのサブセットを移動する場合でも技術的には機能しますが、その場合、多数の参照を同時に更新する必要があるため、移行を大幅に容易にするとは限りません。この機能は、Kotlin、TypeScript、JavaScript、GCL、または protobuf 静的リフレクションを使用する C++ ターゲットでは利用できません。
import public
の依存関係は、import public
ステートメントを含むプロトをインポートする任意のコードによって推移的に依存できます。たとえば、
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
プロトコルコンパイラは、-I
/--proto_path
フラグを使用してプロトコルコンパイラ コマンドラインで指定されたディレクトリのセットでインポートされたファイルを検索します。フラグが指定されていない場合、コンパイラが呼び出されたディレクトリを探します。一般的には、--proto_path
フラグをプロジェクトのルートに設定し、すべてのインポートで完全修飾名を使用する必要があります。
proto2 メッセージ型の使用
proto2 メッセージ型をインポートして proto3 メッセージで使用することは可能であり、その逆も可能です。ただし、proto2 の enum は proto3 の構文で直接使用することはできません(インポートされた proto2 メッセージがそれらを使用する場合は問題ありません)。
ネストされた型
以下の例のように、他のメッセージ型の中にメッセージ型を定義して使用できます。ここでは、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 テキスト形式を使用してプロトコルバッファメッセージを保存する場合、プロト定義で行える変更は異なります。Proto のベストプラクティスと以下のルールを確認してください。
- 既存のフィールドのフィールド番号は変更しないでください。「フィールド番号を変更する」ことは、フィールドを削除し、同じ型で新しいフィールドを追加することと同じです。フィールドを再番号付けしたい場合は、フィールドの削除の指示を参照してください。
- 新しいフィールドを追加した場合、古いメッセージ形式を使用するコードによってシリアル化されたメッセージも、新しい生成コードでパースできます。新しいコードが古いコードによって生成されたメッセージと適切に相互作用できるように、これらの要素のデフォルト値を覚えておく必要があります。同様に、新しいコードによって作成されたメッセージは、古いコードでパースできます。古いバイナリは、パース時に新しいフィールドを単に無視します。不明なフィールドのセクションを参照してください。
- フィールド番号が更新されたメッセージ型で再び使用されない限り、フィールドを削除できます。代わりにフィールド名を変更する(たとえば「OBSOLETE_」というプレフィックスを追加する)か、フィールド番号を予約済みにして、将来の
.proto
ユーザーが誤ってその番号を再利用できないようにすることをお勧めします。 int32
、uint32
、int64
、uint64
、およびbool
はすべて互換性があります。これは、前方互換性または後方互換性を損なうことなく、これらの型のいずれかから別の型にフィールドを変更できることを意味します。ワイヤからパースされた数値が対応する型に収まらない場合、C++ でその数値をその型にキャストした場合と同じ効果が得られます(たとえば、64ビットの数値が int32 として読み取られる場合、32ビットに切り捨てられます)。sint32
とsint64
は互いに互換性がありますが、他の整数型とは互換性がありません。書き込まれた値が INT_MIN から INT_MAX の範囲内(両端を含む)であった場合、どちらの型でも同じ値としてパースされます。sint64 の値がその範囲外で書き込まれ、sint32 としてパースされた場合、varint は32ビットに切り捨てられ、その後ジグザグデコードが行われます(これにより異なる値が観測されます)。string
とbytes
は、バイトが有効な UTF-8 である限り互換性があります。- 埋め込みメッセージは、バイトにメッセージのエンコードされたインスタンスが含まれている場合、
bytes
と互換性があります。 fixed32
はsfixed32
と、fixed64
はsfixed64
と互換性があります。string
、bytes
、およびメッセージフィールドの場合、単一型はrepeated
と互換性があります。繰り返しフィールドのシリアル化されたデータが入力として与えられた場合、このフィールドが単一型であると想定するクライアントは、プリミティブ型フィールドであれば最後の入力値を取得し、メッセージ型フィールドであればすべての入力要素をマージします。ただし、ブール値や enum を含む数値型の場合、これは一般的に安全ではないことに注意してください。数値型の繰り返しフィールドは、デフォルトでパック形式でシリアル化され、単一フィールドが期待される場合には正しくパースされません。enum
は、ワイヤ形式に関してint32
、uint32
、int64
、およびuint64
と互換性があります(ただし、値が収まらない場合は切り捨てられます)。しかし、メッセージがデシリアル化されたときにクライアントコードがそれらを異なる方法で扱う可能性があることに注意してください。例えば、認識されない proto3 のenum
値はメッセージ内に保持されますが、メッセージがデシリアル化されたときにこれがどのように表現されるかは言語に依存します。Int フィールドは常にその値を保持します。- 単一の
optional
フィールドまたは拡張機能を新しいoneof
のメンバーに変更することはバイナリ互換ですが、一部の言語(特に Go)では、生成されたコードの API が互換性のない方法で変更されます。このため、Google は AIP-180 で文書化されているように、公開 API でこのような変更を行いません。ソース互換性に関する同じ注意点として、複数のフィールドを新しいoneof
に移動することは、一度に1つしか設定されないことが確実であれば安全かもしれません。既存のoneof
にフィールドを移動することは安全ではありません。同様に、単一フィールドのoneof
をoptional
フィールドまたは拡張機能に変更することは安全です。 map<K, V>
と対応するrepeated
メッセージフィールドの間でフィールドを変更することはバイナリ互換です(メッセージのレイアウトとその他の制限については、以下のマップを参照)。ただし、変更の安全性はアプリケーションに依存します。メッセージのデシリアル化および再シリアル化を行う際、repeated
フィールド定義を使用するクライアントは意味的に同一の結果を生成しますが、map
フィールド定義を使用するクライアントはエントリの順序を変更したり、重複キーを持つエントリを削除したりする可能性があります。
不明なフィールド
不明なフィールドとは、パーサーが認識しないフィールドを表す、整形式のプロトコルバッファ シリアル化データです。たとえば、古いバイナリが新しいフィールドを持つ新しいバイナリから送信されたデータをパースすると、それらの新しいフィールドは古いバイナリでは不明なフィールドとなります。
Proto3 メッセージは不明なフィールドを保持し、パース中およびシリアル化された出力に含めます。これは proto2 の動作と一致します。
不明なフィールドの保持
一部の操作は、不明なフィールドの損失を引き起こす可能性があります。たとえば、以下のいずれかの操作を行うと、不明なフィールドが失われます。
- プロトを JSON にシリアル化する。
- メッセージ内のすべてのフィールドを反復処理して、新しいメッセージを生成する。
不明なフィールドの損失を避けるには、以下を実行してください。
- バイナリを使用し、データ交換にはテキスト形式の使用を避ける。
- フィールドごとにコピーするのではなく、
CopyFrom()
やMergeFrom()
などのメッセージ指向 API を使用してデータをコピーする。
TextFormat は少し特殊なケースです。TextFormat へのシリアル化では、不明なフィールドがフィールド番号を使用して出力されます。しかし、TextFormat データをバイナリプロトにパースし直すと、フィールド番号を使用するエントリがある場合に失敗します。
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 ...
}
}
Oneof
多くの単一フィールドを持ち、同時に設定されるフィールドが最大で1つであるメッセージがある場合、oneof 機能を使用することでこの動作を強制し、メモリを節約できます。
Oneof フィールドは optional フィールドに似ていますが、oneof 内のすべてのフィールドがメモリを共有し、同時に設定できるフィールドは最大で1つです。oneof の任意のメンバーを設定すると、他のすべてのメンバーが自動的にクリアされます。oneof 内のどの値が設定されているか(もしあれば)は、選択した言語に応じて特別な case()
または WhichOneof()
メソッドを使用して確認できます。
複数の値が設定された場合、プロト内の順序で決定される最後に設定された値が、以前のすべての値を上書きすることに注意してください。
oneof フィールドのフィールド番号は、囲むメッセージ内で一意である必要があります。
Oneof の使用
.proto
で oneof を定義するには、oneof
キーワードの後に oneof 名(この場合は test_oneof
)を使用します。
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
次に、oneof 定義に oneof フィールドを追加します。map
フィールドと repeated
フィールドを除く、任意の型のフィールドを追加できます。oneof に繰り返しフィールドを追加する必要がある場合は、繰り返しフィールドを含むメッセージを使用できます。
生成されたコードでは、oneof フィールドは通常のフィールドと同じゲッターとセッターを持ちます。また、oneof 内のどの値が設定されているか(もしあれば)を確認するための特別なメソッドも提供されます。選択した言語の oneof API の詳細については、関連するAPI リファレンスを参照してください。
Oneof の機能
oneof フィールドを設定すると、その oneof の他のすべてのメンバーが自動的にクリアされます。したがって、複数の oneof フィールドを設定した場合、最後に設定したフィールドのみが値を持つことになります。
SampleMessage message; message.set_name("name"); CHECK_EQ(message.name(), "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.name().empty());
パーサーがワイヤ上で同じ 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_EQ(msg2.name(), "name");
後方互換性の問題
oneof フィールドを追加または削除する際には注意してください。oneof の値をチェックして None
/NOT_SET
が返される場合、それは oneof が設定されていないか、oneof の異なるバージョンでフィールドが設定されたかのいずれかを意味する可能性があります。ワイヤ上の不明なフィールドが oneof のメンバーであるかどうかを知る方法がないため、その違いを区別する方法はありません。
タグ再利用の問題
- 単一フィールドを oneof の内外に移動する: メッセージがシリアル化およびパースされた後、情報の一部が失われる可能性があります(一部のフィールドがクリアされます)。ただし、単一フィールドを新しい oneof に安全に移動でき、一度に1つしか設定されないことが分かっている場合は、複数のフィールドを移動できる可能性があります。詳細については、メッセージ型の更新を参照してください。
- oneof フィールドを削除して再度追加する: これにより、メッセージがシリアル化およびパースされた後、現在設定されている oneof フィールドがクリアされる可能性があります。
- oneof を分割またはマージする: これは単一フィールドの移動と同様の問題を抱えています。
マップ
データ定義の一部として連想マップを作成したい場合、プロトコルバッファは便利なショートカット構文を提供します。
map<key_type, value_type> map_field = N;
…ここで、key_type
は任意の整数型または文字列型にできます(したがって、浮動小数点型と bytes
を除く任意のスカラ型)。enum もプロトメッセージも 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;
マップをサポートするすべてのプロトコルバッファ実装は、以前の定義によって受け入れられるデータを生成し、受け入れる必要があります。
パッケージ
プロトコルメッセージ型間の名前衝突を防ぐために、.proto
ファイルにオプションの package
指定子を追加できます。
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 です。gRPC は、Google で開発された言語およびプラットフォームに依存しないオープンソースの RPC システムです。gRPC はプロトコルバッファと特に相性が良く、特別なプロトコルバッファ コンパイラ プラグインを使用して、関連する RPC コードを .proto
ファイルから直接生成できます。
gRPC を使用したくない場合でも、独自の RPC 実装でプロトコルバッファを使用することは可能です。これについては、Proto2 言語ガイドで詳しく知ることができます。
Protocol Buffers の RPC 実装を開発するための進行中のサードパーティプロジェクトも多数存在します。私たちが知っているプロジェクトのリンク一覧については、サードパーティアドオンの Wiki ページを参照してください。
JSON マッピング
標準の protobuf バイナリ ワイヤ形式は、protobuf を使用する2つのシステム間の通信に推奨されるシリアル化形式です。protobuf ワイヤ形式ではなく JSON を使用するシステムと通信する場合、Protobuf はJSON での標準的なエンコーディングをサポートしています。
オプション
.proto
ファイル内の個々の宣言は、多くのオプションで注釈を付けることができます。オプションは宣言の全体的な意味を変更しませんが、特定のコンテキストでの処理方法に影響を与える可能性があります。利用可能なオプションの完全なリストは、/google/protobuf/descriptor.proto
で定義されています。
一部のオプションはファイルレベルのオプションであり、メッセージ、enum、またはサービスの定義内ではなく、トップレベルスコープに記述する必要があります。一部のオプションはメッセージレベルのオプションであり、メッセージ定義内に記述する必要があります。一部のオプションはフィールドレベルのオプションであり、フィールド定義内に記述する必要があります。オプションは、enum 型、enum 値、oneof フィールド、サービス型、およびサービスメソッドにも記述できます。ただし、現時点ではこれらのいずれにも有用なオプションは存在しません。
最も一般的に使用されるオプションの一部を以下に示します。
java_package
(ファイルオプション): 生成される Java/Kotlin クラスに使用したいパッケージ。.proto
ファイルで明示的なjava_package
オプションが指定されていない場合、デフォルトでプロトパッケージ(.proto
ファイルで「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 などは、この外側のラッパー Java クラスの内部にネストされたクラス/enum などとして生成されます。Java コードを生成しない場合、このオプションは影響しません。option java_outer_classname = "Ponycopter";
java_multiple_files
(ファイルオプション):false
の場合、この.proto
ファイルに対して単一の.java
ファイルのみが生成され、トップレベルのメッセージ、サービス、列挙用に生成されたすべての Java クラス/enum などは、外側のクラス内にネストされます(java_outer_classname
を参照)。true
の場合、トップレベルのメッセージ、サービス、列挙用に生成された各 Java クラス/enum などに対して個別の.java
ファイルが生成され、この.proto
ファイル用に生成されたラッパー Java クラスにはネストされたクラス/enum などは含まれません。これはブール値オプションで、デフォルトはfalse
です。Java コードを生成しない場合、このオプションは影響しません。option java_multiple_files = true;
optimize_for
(ファイルオプション):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
(フィールドオプション): 基本数値型の繰り返しフィールドではデフォルトでtrue
になり、よりコンパクトなエンコーディングが使用されます。アンパックされたワイヤ形式を使用するには、false
に設定できます。これは、以下の例に示すように、バージョン2.3.0より前のパーサーとの互換性を提供します(まれに必要とされます)。repeated int32 samples = 4 [packed = false];
deprecated
(フィールドオプション):true
に設定すると、フィールドが非推奨であり、新しいコードで使用すべきではないことを示します。ほとんどの言語では、これは実際には影響しません。Java では、@Deprecated
アノテーションになります。C++ の場合、非推奨フィールドが使用されるたびに clang-tidy が警告を生成します。将来的には、他の言語固有のコードジェネレータがフィールドのアクセサに非推奨アノテーションを生成する可能性があり、その結果、フィールドを使用しようとするコードをコンパイルする際に警告が発行されます。そのフィールドが誰にも使用されておらず、新しいユーザーがそれを使用するのを防ぎたい場合は、フィールド宣言を予約済みステートメントに置き換えることを検討してください。int32 old_field = 6 [deprecated = true];
Enum 値オプション
Enum 値オプションがサポートされています。deprecated
オプションを使用して、値がもはや使用されるべきではないことを示すことができます。拡張機能を使用してカスタム オプションを作成することもできます。
以下の例は、これらのオプションを追加するための構文を示しています。
import "google/protobuf/descriptor.proto";
extend google.protobuf.EnumValueOptions {
optional string string_name = 123456789;
}
enum Data {
DATA_UNSPECIFIED = 0;
DATA_SEARCH = 1 [deprecated = true];
DATA_DISPLAY = 2 [
(string_name) = "display_value"
];
}
string_name
オプションを読み取る C++ コードは、次のようになるかもしれません。
const absl::string_view foo = proto2::GetEnumDescriptor<Data>()
->FindValueByName("DATA_DISPLAY")->options().GetExtension(string_name);
enum 値とフィールドにカスタムオプションを適用する方法については、カスタム オプションを参照してください。
カスタム オプション
Protocol Buffers では、独自のオプションを定義して使用することもできます。これはほとんどの人には必要のない高度な機能であることに注意してください。独自のオプションを作成する必要があると思われる場合は、詳細についてProto2 言語ガイドを参照してください。カスタムオプションの作成には拡張機能を使用します。これは proto3 ではカスタムオプションにのみ許可されています。
オプションの保持
オプションには保持という概念があり、オプションが生成コードに保持されるかどうかを制御します。オプションはデフォルトでランタイム保持され、生成コードに保持され、生成されたディスクリプタプールでランタイム時に可視であることを意味します。ただし、retention = RETENTION_SOURCE
を設定することで、オプション(またはオプション内のフィールド)がランタイム時に保持されないように指定できます。これはソース保持と呼ばれます。
オプションの保持は、ほとんどのユーザーが心配する必要のない高度な機能ですが、バイナリにオプションを保持するコードサイズコストを支払うことなく特定のオプションを使用したい場合に役立ちます。ソース保持を持つオプションは、protoc
および protoc
プラグインから引き続き参照できるため、コードジェネレータはそれらを使用して動作をカスタマイズできます。
保持はオプションに直接設定できます。例:
extend google.protobuf.FileOptions {
optional 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 はこれを強制し、ターゲット制約に違反がある場合はエラーを発生させます。
一見すると、この機能は不要に見えるかもしれません。カスタムオプションはすべて特定のエンティティのオプションメッセージの拡張であり、すでにそのオプションをその単一エンティティに制約しているためです。しかし、オプションのターゲットは、複数のエンティティ型に適用される共有オプションメッセージがあり、そのメッセージ内の個々のフィールドの使用を制御したい場合に役立ちます。たとえば、
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 {
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
と一緒に使用することはできません。これは、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 バージョンを参照してください。