言語ガイド (proto 2)

プロジェクトでProtocol Buffers言語のproto2リビジョンを使用する方法を説明します。

このガイドでは、プロトコルバッファ言語を使用してプロトコルバッファデータを構造化する方法について説明します。これには、.protoファイルの構文と、.protoファイルからデータアクセス生成クラスを生成する方法が含まれます。プロトコルバッファ言語のproto2リビジョンについて説明します。

エディション構文については、Protobufエディション言語ガイドを参照してください。

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_numberresults_per_page)と1つの文字列(query)です。フィールドには、列挙型や、他のメッセージ型のような複合型を指定することもできます。

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

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

  • 指定された番号は、そのメッセージのすべてのフィールドの中で一意でなければなりません
  • フィールド番号19,000から19,999は、Protocol Buffersの実装のために予約されています。メッセージでこれらの予約済みフィールド番号を使用すると、プロトコルバッファコンパイラは文句を言います。
  • 以前に予約されたフィールド番号や、拡張機能に割り当てられているフィールド番号は使用できません。

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

フィールド番号は決して再利用すべきではありません予約済みリストからフィールド番号を取り出して、新しいフィールド定義で再利用しないでください。フィールド番号の再利用による影響を参照してください。

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

フィールド番号の再利用による影響

フィールド番号を再利用すると、ワイヤーフォーマットメッセージのデコードが曖昧になります。

Protobufのワイヤーフォーマットは無駄がなく、ある定義でエンコードされたフィールドを別の定義でデコードしたことを検出する方法を提供していません。

ある定義を使用してフィールドをエンコードし、その後、その同じフィールドを別の定義でデコードすると、以下のような事態につながる可能性があります。

  • デバッグに費やされる開発者の時間
  • パース/マージエラー(最良のシナリオ)
  • 個人情報/機密情報の漏洩
  • データの破損

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

  • フィールドの番号変更(見た目の美しい番号順にするために行われることがあります)。番号変更は、事実上、番号変更に関わるすべてのフィールドを削除して再追加することになり、互換性のないワイヤーフォーマットの変更をもたらします。

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

    • これは、いくつかの理由により、拡張フィールドで非常に起こりやすい間違いでした。拡張宣言は、拡張フィールドを予約するメカニズムを提供します。

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

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

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

  • 単数形:

    proto2には、2種類の単一フィールドがあります。

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

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

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

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

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

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

新しい繰り返しフィールドにはパックエンコーディングを使用する

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

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

`packed`エンコーディングについての詳細は、Protocol Bufferエンコーディングで確認できます。

requiredは強く非推奨です

必須フィールドの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、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より長くすることはできません。
bytes232以下の任意のバイトシーケンスを含むことができます。
Proto 型C++ 型Java/Kotlin 型[1]Python 型[3]Go 型Ruby 型C# 型PHP 型Dart 型Rust 型
doubledoubledoublefloat*float64Floatdoublefloatdoublef64
floatfloatfloatfloat*float32Floatfloatfloatdoublef32
int32int32_tintintint32Fixnum または Bignum (必要に応じて)intinteger*int32i32
int64int64_tlongint/long[4]*int64Bignumlonginteger/string[6]Int64i64
uint32uint32_tint[2]int/long[4]*uint32Fixnum または Bignum (必要に応じて)uintintegerintu32
uint64uint64_tlong[2]int/long[4]*uint64Bignumulonginteger/string[6]Int64u64
sint32int32_tintintint32Fixnum または Bignum (必要に応じて)intinteger*int32i32
sint64int64_tlongint/long[4]*int64Bignumlonginteger/string[6]Int64i64
fixed32uint32_tint[2]int/long[4]*uint32Fixnum または Bignum (必要に応じて)uintintegerintu32
fixed64uint64_tlong[2]int/long[4]*uint64Bignumulonginteger/string[6]Int64u64
sfixed32int32_tintint*int32Fixnum または Bignum (必要に応じて)intintegerinti32
sfixed64int64_tlongint/long[4]*int64Bignumlonginteger/string[6]Int64i64
boolboolbooleanbool*boolTrueClass/FalseClassboolbooleanboolbool
stringstringStringunicode (Python 2), str (Python 3)*stringString (UTF-8)stringstringStringProtoString
bytesstringByteStringbytes[]byteString (ASCII-8BIT)ByteStringstringListProtoBytes

[1] Kotlinは、Java/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()(ハズラーメソッド)メソッドはfalseを返します。
  • results_per_pageの値(「ゲッター」から返される)は10です。

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

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

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

列挙型

メッセージ型を定義するときに、そのフィールドの1つに、事前に定義された値のリストのいずれか1つだけを持たせたい場合があります。たとえば、各SearchRequestcorpusフィールドを追加したいとします。ここで、corpusはUNIVERSALWEBIMAGESLOCALNEWSPRODUCTS、またはVIDEOのいずれかです。これは、可能な値ごとに定数を持つenumをメッセージ定義に追加することで非常に簡単に実行できます。

以下の例では、すべての可能な値を持つ`Corpus`という`enum`と、`Corpus`型のフィールドを追加しました。

enum Corpus {
  CORPUS_UNSPECIFIED = 0;
  CORPUS_UNIVERSAL = 1;
  CORPUS_WEB = 2;
  CORPUS_IMAGES = 3;
  CORPUS_LOCAL = 4;
  CORPUS_NEWS = 5;
  CORPUS_PRODUCTS = 6;
  CORPUS_VIDEO = 7;
}

message SearchRequest {
  optional string query = 1;
  optional int32 page_number = 2;
  optional int32 results_per_page = 3;
  optional Corpus corpus = 4;
}

列挙型のデフォルト値

`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];

列挙値のエイリアス

異なる列挙型定数に同じ値を割り当てることで、エイリアスを定義できます。これを行うには、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クラスが生成されます。

列挙値を削除することは、永続化されたプロトタイプにとって破壊的な変更となります。値を削除する代わりに、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メッセージ型を定義し、SearchResponseResult型のフィールドを指定することでこれを行うことができます。

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  optional string url = 1;
  optional string title = 2;
  repeated string snippets = 3;
}

定義のインポート

先の例では、`Result`メッセージ型は`SearchResponse`と同じファイルで定義されています。フィールド型として使用したいメッセージ型が既に別の`.proto`ファイルで定義されている場合はどうなるでしょうか。

他の`.proto`ファイルからの定義を*インポート*することで使用できます。別の`.proto`の定義をインポートするには、ファイルの先頭にimport文を追加します。

import "myproject/other_protos.proto";

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

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

`import public`の依存関係は、`import public`文を含むプロトをインポートする任意のコードによって推移的に依存される可能性があります。例えば:

// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

プロトコルコンパイラは、-I/--proto_pathフラグを使用してプロトコルコンパイラのコマンドラインで指定されたディレクトリのセットでインポートされたファイルを検索します。フラグが指定されていない場合、コンパイラが呼び出されたディレクトリを検索します。一般的に、--proto_pathフラグをプロジェクトのルートに設定し、すべてのインポートに完全修飾名を使用する必要があります。

proto3メッセージ型の使用

proto3およびエディション2023のメッセージ型をインポートし、proto2メッセージで使用することが可能です。また、その逆も可能です。ただし、proto2の列挙型を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;
    }
  }
}

グループ

グループ機能は非推奨であり、新しいメッセージ型を作成する際には使用すべきではありません。代わりにネストされたメッセージ型を使用してください。

グループは、メッセージ定義に情報をネストするもう1つの方法です。たとえば、複数のResultを含むSearchResponseを指定する別の方法は次のとおりです。

message SearchResponse {
  repeated group Result = 1 {
    optional string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
}

グループは、ネストされたメッセージ型とフィールドを単一の宣言に結合するだけです。コードでは、このメッセージをresultという名前のResult型フィールドがあるかのように扱うことができます(後者の名前は前者の名前と衝突しないように小文字に変換されます)。したがって、この例は、メッセージが異なるワイヤ形式を持つことを除けば、前述のSearchResponseとまったく同じです。

メッセージ型の更新

既存のメッセージ型がすべてのニーズを満たさなくなった場合(たとえば、メッセージ形式に余分なフィールドを持たせたい場合)でも、古い形式で作成されたコードを使用したい場合でも、心配いりません!バイナリワイヤーフォーマットを使用する場合、既存のコードを壊すことなくメッセージ型を更新するのは非常に簡単です。

Protoのベストプラクティスと以下のルールを確認してください。

バイナリワイヤー形式で安全でない変更

ワイヤー非安全な変更とは、古いスキーマを使用してシリアル化されたデータを、新しいスキーマを使用するパーサーでパースした場合(またはその逆)、破損するスキーマ変更のことです。データのすべてのシリアライザーとデシリアライザーが新しいスキーマを使用していることを知っている場合にのみ、ワイヤー非安全な変更を行ってください。

  • 既存のフィールドのフィールド番号を変更することは安全ではありません。
    • フィールド番号を変更することは、そのフィールドを削除し、同じ型で新しいフィールドを追加することと同等です。フィールドの番号を付け替えたい場合は、フィールドの削除の手順を参照してください。
  • フィールドを既存の`oneof`に移動することは安全ではありません。

バイナリワイヤー形式で安全な変更

ワイヤーセーフな変更とは、データの損失や新たなパース失敗のリスクなしに、スキーマをこの方法で進化させることが完全に安全な変更です。

ワイヤーセーフな変更であっても、特定の言語のアプリケーションコードにとって破壊的な変更となる可能性があることに注意してください。例えば、既存の列挙型に値を追加することは、その列挙型に対する網羅的なswitch文を持つコードにとってコンパイルエラーとなります。そのため、Googleは公開メッセージに対してこれらの種類の変更の一部を避ける場合があります。AIPには、これらの変更のうちどれが安全であるかについてのガイダンスが含まれています。

  • 新しいフィールドの追加は安全です。
    • 新しいフィールドを追加した場合、古いメッセージ形式を使用するコードによってシリアル化されたメッセージは、新しい生成コードによってもパースできます。新しいコードが古いコードによって生成されたメッセージと適切に相互作用できるように、これらの要素のデフォルト値を考慮に入れる必要があります。同様に、新しいコードによって作成されたメッセージは古いコードによってパースできます。古いバイナリはパース時に新しいフィールドを単純に無視します。詳細については、不明なフィールドセクションを参照してください。
  • フィールドの削除は安全です。
    • 更新されたメッセージ型で同じフィールド番号を再び使用してはなりません。代わりにフィールド名を変更することをお勧めします(たとえば、「OBSOLETE_」という接頭辞を追加するなど)、または将来の.protoユーザーが誤って番号を再利用できないように、フィールド番号を予約済みにすることをお勧めします。
  • enumに値を追加することは安全です。
  • 単一の明示的な存在フィールドまたは拡張を**新しい** `oneof`のメンバーに変更することは安全です。
  • 1つのフィールドのみを含む`oneof`を明示的な存在フィールドに変更することは安全です。
  • フィールドを同じ番号と型の拡張に変更することは安全です。

バイナリワイヤー形式で互換性のある変更(条件付きで安全)

ワイヤセーフな変更とは異なり、ワイヤ互換とは、特定の変更の前と後で同じデータをパースできることを意味します。ただし、この種の変更では、データのパースが損失を伴う可能性があります。たとえば、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ビットに切り詰められ、その後ジグザグデコードが行われます(これにより異なる値が観測されます)。
  • `string`と`bytes`は、バイト列が有効なUTF-8である限り互換性があります。
  • 埋め込みメッセージは、バイト列がメッセージのエンコードされたインスタンスを含んでいる場合、`bytes`と互換性があります。
  • `fixed32`は`sfixed32`と互換性があり、`fixed64`は`sfixed64`と互換性があります。
  • `string`、`bytes`、およびメッセージフィールドの場合、単数形は`repeated`と互換性があります。
    • 繰り返しフィールドのシリアル化されたデータが入力として与えられた場合、このフィールドが単一であると期待するクライアントは、プリミティブ型フィールドの場合は最後の入力値を取得し、メッセージ型フィールドの場合はすべての入力要素をマージします。これは、boolやenumを含む数値型の場合には一般的に安全ではないことに注意してください。数値型の繰り返しフィールドは、デフォルトでpacked形式でシリアル化されますが、単一フィールドが期待される場合には正しくパースされません。
  • `enum`は`int32`、`uint32`、`int64`、`uint64`と互換性があります。
    • メッセージがデシリアライズされる際に、クライアントコードがそれらを異なる方法で扱う可能性があることに注意してください。たとえば、認識されないproto3 enum値はメッセージ内に保持されますが、メッセージがデシリアライズされたときにこれがどのように表現されるかは言語に依存します。
  • フィールドを `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];
}

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

プロトタイプのほとんどのインスタンスで設定される標準フィールドには、フィールド番号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以上は、拡張宣言でのみ使用できます。

拡張型の指定

拡張機能は、oneofmapを除く任意のフィールド型を持つことができます。

ネストされた拡張機能 (非推奨)

別のメッセージのスコープ内で拡張機能を宣言できます。

import "common/user_profile.proto";

package puppies;

message Photo {
  extend common.UserProfile {
    optional int32 likes_count = 111;
  }
  ...
}

この場合、この拡張機能にアクセスするC++コードは次のようになります。

UserProfile user_profile;
user_profile.SetExtension(puppies::Photo::likes_count, 42);

言い換えれば、唯一の効果は、likes_countpuppies.Photoのスコープ内で定義されていることです。

これはよくある混乱の原因です。メッセージ型の内部にネストされたextendブロックを宣言しても、外側の型と拡張された型の間に何らかの関係があるわけではありません。特に、前の例はPhotoUserProfileの何らかのサブクラスであることを意味するものではありません。単にシンボルlikes_countPhotoのスコープ内で宣言されていることを意味するだけで、単なる静的メンバーです。

一般的なパターンは、拡張機能のフィールド型のスコープ内で拡張機能を定義することです。たとえば、ここではmedia.UserContentへのpuppies.Photo型の拡張機能の例で、拡張機能は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のいずれかのメンバーを設定すると、他のすべてのメンバーは自動的にクリアされます。oneof内のどの値が設定されているか(もしあれば)は、選択した言語に応じて、特別なcase()またはWhichOneof()メソッドを使用して確認できます。

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

oneofフィールドのフィールド番号は、それを囲むメッセージ内で一意でなければなりません。

Oneofの使用

`.proto`でoneofを定義するには、`oneof`キーワードの後にoneof名、この場合は`test_oneof`を続けます。

message SampleMessage {
  oneof test_oneof {
     string name = 4;
     SubMessage sub_message = 9;
  }
}

そして、oneof定義にoneofフィールドを追加します。mapフィールドを除く任意の型のフィールドを追加できますが、requiredoptional、またはrepeatedキーワードは使用できません。oneofにrepeatedフィールドを追加する必要がある場合は、repeatedフィールドを含むメッセージを使用できます。

生成されたコードでは、oneofフィールドは通常のoptionalフィールドと同じゲッターとセッターを持ちます。また、oneof内でどの値が設定されているか(もしあれば)をチェックするための特別なメソッドも提供されます。選択した言語のoneof APIの詳細については、関連するAPIリファレンスを参照してください。

Oneofの機能

  • oneofフィールドを設定すると、oneofの他のすべてのメンバーが自動的にクリアされます。そのため、複数のoneofフィールドを設定した場合、最後に設定したフィールドのみが値を持ち続けます。

    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    // Calling mutable_sub_message() will clear the name field and will set
    // sub_message to a new instance of SubMessage with none of its fields set.
    message.mutable_sub_message();
    CHECK(!message.has_name());
    
  • パーサーがワイヤ上で同じoneofの複数のメンバーを検出した場合、パースされたメッセージでは最後に検出されたメンバーのみが使用されます。ワイヤ上のデータをパースする際、バイトの先頭から開始し、次の値を評価し、以下のパース規則を適用します。

    • まず、同じoneof内の*異なる*フィールドが現在設定されているかを確認し、設定されていればそれをクリアします。

    • 次に、そのフィールドがoneof内にないかのように内容を適用します。

      • プリミティブは、既に設定されている値を上書きします。
      • メッセージは、すでに設定されている値にマージされます。
  • oneofでは拡張機能はサポートされていません。

  • oneofは`repeated`にできません。

  • リフレクションAPIはoneofフィールドに対して機能します。

  • oneofフィールドをデフォルト値に設定した場合(例えば、int32のoneofフィールドを0に設定するなど)、そのoneofフィールドの「ケース」が設定され、値はワイヤー上でシリアライズされます。

  • C++を使用している場合は、コードがメモリクラッシュを引き起こさないように注意してください。以下のサンプルコードは、`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;

マップの機能

  • マップでは拡張機能はサポートされていません。
  • マップは、repeatedoptionalrequiredにはできません。
  • マップ値のワイヤーフォーマットの順序とマップの反復順序は未定義であるため、マップ項目が特定の順序であることに依存することはできません。
  • `.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_library Bazelルールで指定されたパッケージに属します。オープンソースプロジェクトの場合、go_packageオプションを指定するか、Bazelの-Mフラグを設定するかのいずれか必須とします。
  • Rubyでは、生成されたクラスはネストされたRuby名前空間にラップされ、必要なRubyの大文字/小文字のスタイル(最初の文字が大文字。最初の文字がアルファベットでない場合はPB_が前置される)に変換されます。たとえば、OpenFoo::Bar名前空間に属します。
  • PHPでは、.protoファイルで明示的にoption php_namespaceを提供しない限り、パッケージはPascalCaseに変換されてから名前空間として使用されます。たとえば、OpenFoo\Bar名前空間に属します。
  • C#では、.protoファイルで明示的にoption csharp_namespaceを指定しない限り、パッケージはPascalCaseに変換されてから名前空間として使用されます。たとえば、OpenFoo.Bar名前空間に属します。

Pythonのようにpackageディレクティブが生成コードに直接影響しない場合でも、.protoファイルにパッケージを指定することを強く推奨します。そうしないと、記述子で名前の競合が発生し、他の言語でプロトタイプが移植できなくなる可能性があるためです。

パッケージと名前解決

プロトコルバッファ言語における型名解決はC++のように機能します。まず最も内側のスコープが検索され、次にその次の内側のスコープというように、各パッケージは親パッケージの「内側」にあると見なされます。先頭の「.」(例: .foo.bar.Baz)は、最も外側のスコープから開始することを意味します。

プロトコルバッファコンパイラは、インポートされた`.proto`ファイルを解析することですべての型名を解決します。各言語のコードジェネレータは、異なるスコーピングルールを持っていても、その言語で各型を参照する方法を知っています。

サービスの定義

RPC(リモートプロシージャコール)システムでメッセージ型を使用したい場合は、.protoファイルでRPCサービスインターフェースを定義でき、プロトコルバッファコンパイラは選択した言語でサービスインターフェースコードとスタブを生成します。したがって、たとえば、SearchRequestを受け取りSearchResponseを返すメソッドを持つRPCサービスを定義したい場合は、.protoファイルで次のように定義できます。

service SearchService {
  rpc Search(SearchRequest) returns (SearchResponse);
}

デフォルトでは、プロトコルコンパイラはSearchServiceという抽象インターフェースと、対応する「スタブ」実装を生成します。スタブはすべての呼び出しをRpcChannelに転送し、RpcChannelは、独自のRPCシステムに関して自分で定義する必要がある抽象インターフェースです。たとえば、メッセージをシリアル化してHTTP経由でサーバーに送信するRpcChannelを実装することができます。言い換えれば、生成されたスタブは、特定のRPC実装に縛られることなく、プロトコルバッファベースのRPC呼び出しを行うための型安全なインターフェースを提供します。そのため、C++では、最終的に次のようなコードになる可能性があります。

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

void DoSearch() {
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = new MyRpcChannel("somehost.example.com:1234");
  controller = new MyRpcController;

  // The protocol compiler generates the SearchService class based on the
  // definition given earlier.
  service = new SearchService::Stub(channel);

  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, &request, &response,
                  protobuf::NewCallback(&Done));
}

void Done() {
  delete service;
  delete channel;
  delete controller;
}

すべてのサービス・クラスはまた、Serviceインターフェースを実装しています。これは、コンパイル時にメソッド名やその入力および出力型を知らなくても、特定のメソッドを呼び出す方法を提供します。サーバー側では、これを使用してサービスを登録できるRPCサーバーを実装することができます。

using google::protobuf;

class ExampleSearchService : public SearchService {
 public:
  void Search(protobuf::RpcController* controller,
              const SearchRequest* request,
              SearchResponse* response,
              protobuf::Closure* done) {
    if (request->query() == "google") {
      response->add_result()->set_url("http://www.google.com");
    } else if (request->query() == "protocol buffers") {
      response->add_result()->set_url("http://protobuf.googlecode.com");
    }
    done->Run();
  }
};

int main() {
  // You provide class MyRpcServer.  It does not have to implement any
  // particular interface; this is just an example.
  MyRpcServer server;

  protobuf::Service* service = new ExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return 0;
}

既存のRPCシステムを接続したくない場合は、Googleで開発された言語とプラットフォームに依存しないオープンソースのRPCシステムであるgRPCを使用できます。gRPCはプロトコルバッファと特にうまく機能し、特別なプロトコルバッファコンパイラプラグインを使用して.protoファイルから関連するRPCコードを直接生成できます。ただし、proto2とproto3で生成されたクライアントとサーバーの間には潜在的な互換性の問題があるため、gRPCサービスを定義するにはproto3またはエディション2023を使用することをお勧めします。proto3構文の詳細についてはProto3言語ガイドを、エディション2023についてはエディション2023言語ガイドを参照してください。

gRPCに加えて、プロトコルバッファ用のRPC実装を開発するサードパーティプロジェクトもいくつか進行中です。私たちが知っているプロジェクトへのリンクのリストについては、サードパーティアドオンのwikiページを参照してください。

JSONマッピング

標準のprotobufバイナリワイヤ形式は、protobufを使用する2つのシステム間の通信に推奨されるシリアル化形式です。protobufワイヤ形式ではなくJSONを使用するシステムとの通信には、ProtobufはJSONでの正規エンコーディングをサポートしています。

オプション

.protoファイル内の個々の宣言には、多くのオプションを注釈として付けることができます。オプションは宣言の全体的な意味を変更しませんが、特定のコンテキストでの処理方法に影響を与える可能性があります。利用可能なオプションの完全なリストは、/google/protobuf/descriptor.protoで定義されています。

一部のオプションはファイルレベルオプションであり、メッセージ、enum、またはサービス定義内ではなく、トップレベルスコープで記述する必要があります。一部のオプションはメッセージレベルオプションであり、メッセージ定義内に記述する必要があります。一部のオプションはフィールドレベルオプションであり、フィールド定義内に記述する必要があります。オプションは、enum型、enum値、oneofフィールド、サービス型、およびサービスメソッドにも記述できますが、現在、これらのいずれにも有用なオプションは存在しません。

以下は、最も一般的に使用されるオプションのいくつかです。

  • java_package (ファイルオプション): 生成されるJava/Kotlinクラスに使用したいパッケージ。.protoファイルで明示的なjava_packageオプションが指定されていない場合、デフォルトでprotoパッケージ(.protoファイルで"package"キーワードを使用して指定)が使用されます。しかし、protoパッケージは通常、リバースドメイン名で始まることが期待されていないため、良いJavaパッケージとはなりません。JavaまたはKotlinコードを生成しない場合、このオプションは効果がありません。

    option java_package = "com.example.foo";
    
  • java_outer_classname(ファイルオプション):生成したいラッパーJavaクラスのクラス名(およびファイル名)。.protoファイルで明示的なjava_outer_classnameが指定されていない場合、クラス名は.protoファイル名をキャメルケースに変換して構築されます(foo_bar.protoFooBar.javaになります)。java_multiple_filesオプションが無効の場合、.protoファイル用に生成される他のすべてのクラス/列挙型などは、この外側のラッパーJavaクラスの内部にネストされたクラス/列挙型などとして生成されます。Javaコードを生成しない場合、このオプションは効果がありません。

    option java_outer_classname = "Ponycopter";
    
  • java_multiple_files(ファイルオプション):falseの場合、この.protoファイルに対して単一の.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クラスとenumの先頭に付加されるObjective-Cクラス接頭辞を設定します。デフォルト値はありません。Appleが推奨するように、3~5文字の大文字の接頭辞を使用する必要があります。2文字の接頭辞はすべてAppleによって予約されていることに注意してください。

  • message_set_wire_format (メッセージオプション): true に設定すると、メッセージは Google 内部で使用されていた MessageSet と呼ばれる古い形式との互換性を意図した異なるバイナリ形式を使用します。Google 外部のユーザーがこのオプションを使用する必要があることはおそらくありません。メッセージは厳密に次のように宣言されなければなりません。

    message Foo {
      option message_set_wire_format = true;
      extensions 4 to max;
    }
    
  • packed(フィールドオプション):基本的な数値型の繰り返しフィールドでtrueに設定すると、よりコンパクトなエンコーディングが使用されます。このオプションを使用しない唯一の理由は、バージョン2.3.0以前のパーサーとの互換性が必要な場合です。これらの古いパーサーは、予期しない場合、パックされたデータを無視します。したがって、既存のフィールドをパック形式に変更すると、ワイヤ互換性が損なわれる可能性がありました。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で定義されたメッセージ(FileOptionsFieldOptionsなど)によって定義されるため、独自のオプションを定義することは、それらのメッセージを拡張することにすぎません。例:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}

message MyMessage {
  option (my_option) = "Hello world!";
}

ここでは、MessageOptionsを拡張して新しいメッセージレベルオプションを定義しました。このオプションを使用する際、拡張機能であることを示すためにオプション名を括弧で囲む必要があります。C++でmy_optionの値を読み取る方法は次のとおりです。

string value = MyMessage::descriptor()->options().GetExtension(my_option);

ここでは、MyMessage::descriptor()->options()MyMessageMessageOptionsプロトコルメッセージを返します。カスタムオプションの読み取りは、他の拡張機能の読み取りと同じです。

同様に、Javaでは次のように記述します。

String value = MyProtoFile.MyMessage.getDescriptor().getOptions()
  .getExtension(MyProtoFile.myOption);

Pythonでは次のようになります。

value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
  .Extensions[my_proto_file_pb2.my_option]

カスタムオプションは、プロトコルバッファ言語のあらゆる種類の構造に対して定義できます。あらゆる種類のオプションを使用した例を次に示します。

import "google/protobuf/descriptor.proto";

extend google.protobuf.FileOptions {
  optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
  optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
  optional float my_field_option = 50002;
}
extend google.protobuf.OneofOptions {
  optional int64 my_oneof_option = 50003;
}
extend google.protobuf.EnumOptions {
  optional bool my_enum_option = 50004;
}
extend google.protobuf.EnumValueOptions {
  optional uint32 my_enum_value_option = 50005;
}
extend google.protobuf.ServiceOptions {
  optional MyEnum my_service_option = 50006;
}
extend google.protobuf.MethodOptions {
  optional MyMessage my_method_option = 50007;
}

option (my_file_option) = "Hello world!";

message MyMessage {
  option (my_message_option) = 1234;

  optional int32 foo = 1 [(my_field_option) = 4.5];
  optional string bar = 2;
  oneof qux {
    option (my_oneof_option) = 42;

    string quux = 3;
  }
}

enum MyEnum {
  option (my_enum_option) = true;

  FOO = 1 [(my_enum_value_option) = 321];
  BAR = 2;
}

message RequestType {}
message ResponseType {}

service MyService {
  option (my_service_option) = FOO;

  rpc MyMethod(RequestType) returns(ResponseType) {
    // Note:  my_method_option has type MyMessage.  We can set each field
    //   within it using a separate "option" line.
    option (my_method_option).foo = 567;
    option (my_method_option).bar = "Some string";
  }
}

カスタムオプションを、それが定義されたパッケージとは別のパッケージで使用したい場合は、型名と同様に、オプション名の前にパッケージ名を付ける必要があることに注意してください。例:

// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
  option (foo.my_option) = "Hello world!";
}

最後に一つ:カスタムオプションは拡張機能であるため、他のフィールドや拡張機能と同様にフィールド番号を割り当てる必要があります。上記の例では、50000-99999の範囲のフィールド番号を使用しています。この範囲は個々の組織内で内部使用のために予約されているため、社内アプリケーションではこの範囲の番号を自由に使うことができます。ただし、公開アプリケーションでカスタムオプションを使用する場合は、フィールド番号がグローバルに一意であることを確認することが重要です。グローバルに一意なフィールド番号を取得するには、protobufグローバル拡張レジストリにエントリを追加するリクエストを送信してください。通常、必要な拡張番号は1つだけです。サブメッセージ内に複数のオプションを配置することで、1つの拡張番号で複数のオプションを宣言できます。

message FooOptions {
  optional int32 opt1 = 1;
  optional string opt2 = 2;
}

extend google.protobuf.FieldOptions {
  optional FooOptions foo_options = 1234;
}

// usage:
message Bar {
  optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
  // alternative aggregate syntax (uses TextFormat):
  optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}

また、各オプション型(ファイルレベル、メッセージレベル、フィールドレベルなど)には独自の番号空間があるため、たとえば、FieldOptionsとMessageOptionsの拡張機能を同じ番号で宣言できることにも注意してください。

オプションの保持

オプションには、生成されたコードにオプションが保持されるかどうかを制御する保持の概念があります。オプションはデフォルトでランタイム保持されており、生成されたコードに保持され、生成された記述子プールでランタイム時に可視です。ただし、retention = RETENTION_SOURCEを設定して、オプション(またはオプション内のフィールド)をランタイム時に保持しないように指定できます。これはソース保持と呼ばれます。

オプションの保持は、ほとんどのユーザーが気にする必要のない高度な機能ですが、バイナリに保持するコードサイズコストを支払うことなく特定のオプションを使用したい場合に役立ちます。ソース保持を持つオプションは依然としてprotocおよびprotocプラグインに可視であるため、コードジェネレータはそれらを使用して動作をカスタマイズできます。

保持は、次のようにオプションに直接設定できます。

extend google.protobuf.FileOptions {
  optional int32 source_retention_option = 1234
      [retention = RETENTION_SOURCE];
}

通常のフィールドにも設定できますが、その場合、そのフィールドがオプション内に現れる場合にのみ効果があります。

message OptionsMessage {
  optional int32 source_retention_field = 1 [retention = RETENTION_SOURCE];
}

retention = RETENTION_RUNTIME を設定することもできますが、これはデフォルトの動作であるため効果はありません。メッセージフィールドがRETENTION_SOURCEとマークされている場合、その内容全体が破棄されます。その内部のフィールドは、RETENTION_RUNTIMEを設定しようとしてもそれをオーバーライドすることはできません。

オプションのターゲット

フィールドにはtargetsオプションがあり、オプションとして使用される場合にフィールドが適用されるエンティティのタイプを制御します。たとえば、フィールドにtargets = TARGET_TYPE_MESSAGEがある場合、そのフィールドはenum(または他の非メッセージエンティティ)のカスタムオプションで設定できません。Protocはこれを強制し、ターゲット制約の違反がある場合はエラーを発生させます。

一見すると、この機能は不必要に見えるかもしれません。なぜなら、すべてのカスタムオプションは特定のエンティティのオプションメッセージの拡張であり、すでにそのオプションをその1つのエンティティに制限しているからです。しかし、オプションターゲットは、複数のエンティティタイプに適用される共有オプションメッセージがあり、そのメッセージ内の個々のフィールドの使用を制御したい場合に役立ちます。例えば、

message MyOptions {
  optional string file_only_option = 1 [targets = TARGET_TYPE_FILE];
  optional int32 message_and_enum_option = 2 [targets = TARGET_TYPE_MESSAGE,
                                              targets = TARGET_TYPE_ENUM];
}

extend google.protobuf.FileOptions {
  optional MyOptions file_options = 50000;
}

extend google.protobuf.MessageOptions {
  optional MyOptions message_options = 50000;
}

extend google.protobuf.EnumOptions {
  optional MyOptions enum_options = 50000;
}

// OK: this field is allowed on file options
option (file_options).file_only_option = "abc";

message MyMessage {
  // OK: this field is allowed on both message and enum options
  option (message_options).message_and_enum_option = 42;
}

enum MyEnum {
  MY_ENUM_UNSPECIFIED = 0;
  // Error: file_only_option cannot be set on an enum.
  option (enum_options).file_only_option = "xyz";
}

クラスの生成

.protoファイルで定義されたメッセージ型を操作するために必要なJava、Kotlin、Python、C++、Go、Ruby、Objective-C、またはC#コードを生成するには、.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.protoproto/lib2/data.protoがある場合、-I=proto/lib1 -I=proto/lib2と組み合わせてこれらの2つのファイルを使用することはできません。なぜなら、import "data.proto"がどちらのファイルを意味するのか曖昧になるためです。代わりに-Iproto/を使用し、グローバル名はlib1/data.protolib2/data.protoになります。

ライブラリを公開しており、他のユーザーがメッセージを直接使用する可能性がある場合は、ファイル名の衝突を避けるために、パスに一意のライブラリ名を含めて使用することが期待されます。1つのプロジェクトに複数のディレクトリがある場合、プロジェクトのトップレベルディレクトリに1つの-Iを設定するのが最善の方法です。

  • 1つ以上の出力指示を提供できます。

    追加の利便性として、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バージョンについては、Supported PHP versionsを参照してください。