LoginSignup
151

More than 5 years have passed since last update.

Proto3 Language Guide(和訳)

Last updated at Posted at 2017-05-11

これはなに

Protocol Buffers バージョン3の Language Guide の訳がありそうで見つからなかったので訳した.

原文:https://developers.google.com/protocol-buffers/docs/proto3

Portions of this page are modifications based on work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License and Apache 2.0 License for code samples.

Language Guide (proto3)

このガイドは,プロトコルバッファーデータを組み立てるために,どのようにプロトコルバッファー言語を使用するかについて述べる.ガイドには,.proto ファイルのシンタックスについてと,.proto ファイルからデータアクセスクラスをどのように生成するかについての記述を含む.このガイドは proto3 バージョンについてカバーしている.古い proto2 バージョンについては,Proto2 Language Guide を参照されたい.

これはレファレンス・ガイドである――このドキュメントで述べられているたくさんの特徴を用いたステップ・バイ・ステップの例については,言語別の tutorial を参照されたい(現在は proto2 のみである;より多くの proto3 ドキュメンテーションについては近日公開).

メッセージタイプを定義する

はじめにとても簡単な例を見てみよう.検索リクエストメッセージフォーマットを定義したいとする.各検索リクエストはクエリ文字列,興味のあるページの番号,1ページあたりの結果の数を含む.これがこのメッセージタイプを定義する .proto ファイルである.

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • ファイルの最初の行では proto3 のシンタックスを用いていることを明記している.これを記述しないと,プロトコルバッファーのコンパイラは proto2 を用いていると考える.この記述は,空でなくコメント行でないファイルの最初の行になければならない.

  • SearchRequest メッセージ定義は,このメッセージ型の中に含めたいデータに対する3つのフィールドを明記している(名前 / 値のペア).各フィールドは名前と型を持つ.

フィールドの型指定

上記の例に於いて,全てのフィールドはスカラー型である.即ち,2つの整数 (page_number, result_per_page)と文字列 (query) である.フィールドには,列挙型や他のメッセージ型などの複合型を指定することもできる.

タグの割り当て

既に見たように,メッセージ定義の各フィールドはユニークな数値タグを持つ.これらのタグはメッセージのバイナリフォーマットに於いてフィールドを区別するために使われる.一度あなたのメッセージ型が使われたら,もはやタグを変えるべきではない.留意すべきことだが,1 から 15 までのタグは,数値とフィールドの型を含めて 1 バイトでエンコードされる(Protocol Buffer Encoding に詳しい).16 から 2047 までのタグは 2 バイト用いる.よって,非常によく使われるメッセージ要素のために 1 から 15 までのタグは取っておくべきである.将来加えられるかもしれない非常によく使われる要素のために,ある程度の余地を残すということを覚えておけ.

指定できる最小のタグ値は 1 で,最大は 229-1, 即ち 536,870,911 である.また,19000 から 19999 まで(FieldDescriptor::kFirstReservedNumber から FieldDescriptor::kLastReservedNumber まで)の数値は使ってはいけない.これはプロトコルバッファーの実装によって予約されている.もし .proto ファイル内でこの数値を使った場合,プロトコルバッファーコンパイラは文句を垂れるだろう.同様に,前もって予約されたタグは使ってはならない.

フィールド規則指定

メッセージフィールドは以下のいずれかになれる.

  • singular: 正しい形式のメッセージは,このフィールドについて 0 つか 1 つの値を持つことができる (1 つより多くは持てない).

  • repeated: 正しい形式のメッセージに於いて,このフィールドは何回でも繰り返されうる(0 回を含む).繰り返される値の順番は保存される.

proto3 では,スカラー数値型の repeated フィールドはデフォルトで packed エンコーディングを用いる.

packed エンコーディングについては Protocol Buffer Encoding に詳しい.

他のメッセージ型を追加する

1 つの .proto ファイル内には複数のメッセージ型が定義されうる.複数の関係したメッセージを定義したい時には便利だ――例えば,あなたの SearchResponse メッセージ型に対する返信メッセージのフォーマットを定義したいならば,同じ .proto ファイルに加えることができる.

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

コメント

コメントを加えたいならば,C/C++ スタイルの // シンタックスを用いるがよい.

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  int32 result_per_page = 3;  // Number of results to return per page.
}

予約フィールド

もしあるフィールドを完全に取り除いたりコメントアウトしてメッセージ型を更新した場合,未来のユーザはその型に対して独自の更新を行うとき,そのタグ番号を再使用するかもしれない.彼らが同じ .proto の古いバージョンを読み込んだ時,問題が発生する.データの破損,プライバシーについてのバグ,などなど.このようなことが起きないようにする一つの方法は,削除したフィールドのタグ(もしくはフィールド名,例えば JSON のシリアライズ周りでの問題がある)が reserved であると明記しておくことだ.未来のユーザがそれらのフィールド識別子を使おうとすると,プロトコルバッファーコンパイラは文句を垂れるだろう.

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

同じ reserved ステートメント内にフィールド名とタグ番号を混ぜることはできないことに留意されたい.

.proto ファイルから何が生成されるか?

.proto ファイルをプロトコルバッファーコンパイラにかけると,コンパイラはあなたの選択した言語のコードを生成する.生成されるコードには,定義したメッセージ型や,フィールドのゲッター / セッター,出力のためのシリアライズ,入力されたデータのパースなどが含まれる.

  • C++ では,コンパイラは各 .proto に対して .h.cc ファイルを生成する.ファイルには定義された各メッセージ型に対するクラスが含まれる.

  • Java では,コンパイラは各メッセージ型に対するクラスや,クラスインスタンスを生成するための Builder クラスを含んだ .java ファイルを生成する.

  • Python は少々異なる――Python コンパイラは .proto 内の各メッセージ型の静的デスクリプタを含んだモジュールを生成する.それは必要な Python データアクセスクラスをランタイムに生成するためのメタクラスと共に用いられる.

  • Go では,コンパイラは各メッセージ型に対する型を含んだ .pb.go ファイルを生成する.

  • Ruby では,コンパイラはメッセージ型を含んだ Ruby モジュールを持つ .rb ファイルを生成する.

  • JavaNano では,コンパイラの出力は Java に似ているが,Builder クラスがない.

  • Objective-C では,コンパイラは各 .proto ファイルに対して pbobjc.hpbobjc.m ファイルを生成する.生成されたファイルには定義された各メッセージ型が含まれる.

  • C# では,コンパイラは各 .proto ファイルに対して,定義されたメッセージ型を含んだ .cs ファイルを生成する.

各言語についてのチュートリアルを追うことで,それぞれの言語での API の使い方についてより学ぶことができるだろう(proto3 バージョンは近日公開).API の詳細については API Reference を参照されたい(proto3 バージョンについてはもう少し待て).

スカラー値型

スカラーメッセージフィールドは以下の型の1つを持つ.以下の表は .proto ファイルで指定される型と,対応する自動生成クラス内での型を示す.

.proto Type Notes C++ Type Java Type Python Type[2] Go Type Ruby Type C# Type PHP Type
double double double float float64 Float double float
float float float float float32 Float float float
int32 可変長エンコーディング.負数に対して非効率.負数を持つ虞がある場合は sint32 を用いよ. int32 int int int32 Fixnum or Bignum (as required) int integer 
int64 可変長エンコーディング.負数に対して非効率.負数を持つ虞がある場合は sint64 を用いよ. int64 long int/long[3] int64 Bignum long integer/string[5]
uint32 可変長エンコーディング uint32 int[1] int/long[3] uint32 Fixnum or Bignum (as required) uint integer
uint64 可変長エンコーディング uint64 long[1] int/long[3] uint64 Bignum ulong integer/string[5]
sint32 可変長エンコーディング.符号付き int.int32 より負数に対して高効率. int32 int int int32 Fixnum or Bignum (as required) int integer
sint64 可変長エンコーディング.符号付き int.int64 より負数に対して高効率. int64 long int/long[3] int64 Bignum long integer/string[5]
fixed32 常に 4 バイト.値が屡々 228 を超える場合,uint32 より高効率. uint32 int[1] int uint32 Fixnum or Bignum (as required) uint integer
fixed64 常に 8 バイト.値が屡々 256 を超える場合,uint64 より高効率. uint64 long[1] int/long[3] uint64 Bignum ulong integer/string[5]
sfixed32 常に 4 バイト int32 int int int32 Fixnum or Bignum (as required) int integer
sfixed64 常に 8 バイト int64 long int/long[3] int64 Bignum long integer/string[5]
bool bool boolean bool bool TrueClass/FalseClass bool boolean
string UTF-8 エンコードされた,もしくは 7 ビット ASCII テキストでなければならない. string String str/unicode[4] string String (UTF-8) string string
bytes 任意のバイト列 string ByteString str []byte String (ASCII-8BIT) ByteString string

メッセージのシリアライズ時にこれらの型がどのようにエンコードされるかについては Protocol Buffer Encoding に詳しい.

[1] Java でに於いて,符号なし 32 ビット及び 64 ビット整数は,最上位ビットを単に符号ビットに格納することによって,符号付き整数として表される.

[2] 全ての場合に於いて,フィールドへ値を設定する時に型チェックが行われる.

[3] 64 ビットもしくは符号なし 32 ビット整数は,デコード時常に long として表される.しかしフィールドに値を設定する際にある int が与えられた場合は int になりうる.その値はセット時,表されている型に一致しなければならない.[2] も見よ.

[4] Python 文字列はデコード時 unicode として表される.しかし ASCII 文字列が与えられた場合は str になりうる(この挙動は変更される可能性がある).

[5] 64 ビットマシンでは Integer, 32 ビットマシンでは string が用いられる.

デフォルト値

メッセージがパースされる時,エンコードされたメッセージがある特定の singular 要素を含んでいなければ,対応するフィールドはそのフィールドのデフォルト値にセットされる.これらのデフォルト値は型により異なる.

  • string ではデフォルト値は空文字列である.

  • bytes ではデフォルト値は空バイト列である.

  • bool ではデフォルト値は false である.

  • 数値型ではデフォルト値はゼロである.

  • 列挙型 (enum) ではデフォルト値は最初に定義されている列挙値であり,これは 0 でなければならない.

  • メッセージフィールドでは,フィールドはセットされない.実際の値は言語依存である.詳細は generated code guide を参照されたい.

repeated フィールドに対するデフォルト値は空(一般に妥当な言語では空リスト)である.

スカラー値のフィールドについては,一度メッセージがパースされたが最後,フィールドが明示的にデフォルト値にセットされたのか,それともセットされなかったのかについて伝える方法はないということに留意されたい(例えば,boolean が false にセットされたかどうか).メッセージ型を定義するときにはこのことを心に留めておくべきである.例えば,もしデフォルトでそのような振る舞いを起こしたくないのであれば,false にセットされたときにある振る舞いを変更するような boolean を持つべきではない.また,あるスカラー値のフィールドがそのデフォルト値にセットされたとき,その値はワイヤ上でシリアライズされないことにも注意すべきである.

デフォルトが生成されたコードにおいてどう機能するのかについての詳細は generated code guide を参照されたい.

列挙型

メッセージ型を定義するとき,そのフィールドの一つを,ある事前に定義された値のリストのうちの一つの値を持つようにしたいかもしれない.例えば,各 SearchRequest に対して corpus フィールドを追加したいとする.corpusUNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCT, VIDEO のいずれかの値になるとする.これは単にメッセージ定義の中に,各値について定数値を持つ enum を追加すれば良い.

以下の例では Corpus という enum と,Corpus 型のフィールドを追加した.

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

Corpus enum の最初の定数は 0 にマップされている.全ての enum 定義は必ず最初の要素として 0 へマップされた定数を持たなければならない.これは次の理由による.

  • 必ずゼロ値が存在することになるため,数値のデフォルト値として 0 を用いることができる.

  • ゼロ値は最初の要素である必要がある,これは enum の最初の値が常にデフォルトであった proto2 セマンティクスとの互換性のためである.

異なる enum 定数について同じ値を割り当てることでエイリアスを定義することができる.これをするためには allow_alias オプションを true にする必要がある.さもなくばコンパイラはエラーメッセージを出力するだろう.

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}

列挙子定数の値は 32 ビット整数の範囲内でなければならない.enum 値はワイヤ上で variant encoding を用いているため,負数は非効率でありお勧めしない.上のようにメッセージ定義の中で enum を定義することもできるし,外でも良い――それらの enum.proto ファイル中の他のメッセージ定義で再使用されるだろう.また,異なるメッセージの中にある enum 型を他のメッセージの中でフィールドの型として用いることができる.これには MessageType.EnumType のようなシンタックスを用いる.

enum を用いている .proto ファイルをプロトコルバッファーコンパイラにかける時,生成されるコードには Java や C++ では対応する enum が,Python では特別な EnumDescriptor クラスが含まれるだろう.EnumDescriptor クラスは,ランタイムに生成されたクラス中で整数値を持った記号定数のセットを生成するために使われる.

デシリアライズ中,認識されない enum 値はメッセージ中で保持されるが,メッセージがデシリアライズされた時にどのように表されるかは言語依存である.C++ や Go のような,値が特定のシンボルの範囲外にあるオープン列挙型をサポートしている言語では,不明な enum 値は単にその下層にある整数表現として格納される.Java のようなクローズド列挙型の言語では,enum 中のケースが不正な値を表現するために使われ,下層の整数には特別なアクセサによりアクセスできる.何れの場合においても,メッセージがシリアライズされるときは,認識されない値もメッセージと一緒にシリアライズされる.

アプリケーション中で enum とメッセージがどのように機能するかについての詳細は generated code guide を参照されたい.

他のメッセージ型を使う

他のメッセージ型をフィールドの型として使うことができる.例えば,各 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 "myproject/other_protos.proto";

デフォルトでは直接インポートされた .proto ファイルの定義のみ使うことができる.しかし,時に .proto ファイルを新しい場所へ移動したいこともあるだろう..proto ファイルを直接動かして一挙に全ての呼び出し元を更新する代わりに,古い場所にダミーの .proto ファイルを置いて,新しい場所へフォワーディングすることができる.これには import public の概念を用いる.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 のメッセージ型を用いる

proto2 のメッセージ型をインポートして proto3 のメッセージ中で使うこと,もしくはその逆も可能である.しかしながら,proto2 の enum は proto3 のシンタックスでは直接は使えない(インポートされた proto2 メッセージがそれらを使っている分には ok である).

入れ子型

他のメッセージ型の中でメッセージタイプを定義したり使ったりすることができる.次の例では,SearchResponse メッセージ中で Result メッセージが定義されている.

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;
}

メッセージは好きなだけネストできる.

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;
    }
  }
}

メッセージ型を更新する

もしあるメッセージ型がもはやあなたのニーズに合わなくなった時――例えば,追加のフィールドが欲しくなった時――でも古いフォーマットによって作成されたコードも依然として使いたい時,心配は無用だ.既にあるコードを破壊せずにメッセージ型を更新するのは非常に単純である.ただ以下のルールを覚えておけ:

  • 既に存在するいかなるフィールドの数値タグをも変えてはならない.

  • 新しいフィールドを追加する場合,あなたの”古い”メッセージフォーマットを使っているコードによってシリアライズされた全てのメッセージは,依然として新しく生成されたコードによってもパースすることができる.新しいコードが古いコードによって生成されたメッセージと正しく相互作用できるように,それらの要素についてのデフォルト値については留意しなければならない.同様に,新しいコードによって生成されたメッセージは古いコードによってパースすることができる:古いバイナリはパース時に単に新しいフィールドを無視する.詳細は未知のフィールドのセクションを見よ.

  • フィールドは,更新されたメッセージ型でタグ値が再使用されていない限り,取り除くことができる.フィールドを削除する代わりにリネームすることもあるかもしれない.例えば "OBSOLETE_" というプリフィクスをつけたり,タグを reserved にするなど.これにより未来のユーザがうっかりその番号を再使用してしまうことを防げる.

  • int32, uint32, int64, uint64, bool は全て互換である――即ち,前方あるいは後方互換性を壊すことなく,フィールドの型をこれらの一つから他のものへ変更することができる.もし対応する型に一致しないワイヤから数値がパースされた場合,C++ で数値をその型にキャストしたのと同じような効果が得られる(例えば 64 ビットの数が int32 として読まれた場合,32 ビットに切り詰められる).

  • sint32sint64 は互換だが,他の整数型とは互換でない.

  • stirngbytes は,bytes が有効な UTF-8 である場合に限り互換である.

  • 埋め込まれたメッセージは,もし bytes がそのメッセージのエンコードされたバージョンを含んでいれば,bytes と互換である.

  • fixed32sfixed32 と互換である.また,fixed64sfixed64 も互換である.

  • enumint32, uint32, int64, uint64 と,ワイヤフォーマットの面において互換である(ただしもしフィットしなければ切り詰められる).しかしメッセージがデシリアライズされた時,クライアントのコードはそれらを異なるように扱うかもしれないことに注意されたい:例えば,認識されない proto3の enum 型はメッセージ中で保持はされるが,メッセージがデシリアライズされた時にどう表されるかは言語依存である.Int フィールドは常に単にそれらの値を保存する.

未知のフィールド

未知の (unknown) フィールドは,パーサが認識しないフィールドを表す,正しいプロトコルバッファーのシリアライズデータである.例えば,古いバイナリが新しいフィールドを持った新しいバイナリによって送られたデータをパースする際,それらの新しいフィールドは古いバイナリ中では未知のフィールドとなる.

Proto3 の実装では未知のフィールドを含むメッセージをパースすることはできるが,実装はそれらの未知のフィールドの保持についてはサポートするかもしれないし,しないかもしれない.保持されたかどうかわからない未知のフィールドに依拠するべきではない.殆どの Google プロトコルバッファーの実装では,proto3 に於ける未知のフィールドに,対応する proto ランタイムを通じてアクセスすることはできない.またデシリアライズ時に落とされるか,忘れられる.これは proto2 の挙動とは異なる.proto2 では,未知のフィールドは常に保持され,メッセージと合わせてシリアライズされる.

Any

Any メッセージ型を用いると,メッセージを埋め込まれた型として,それらの .proto 定義を持つことなく利用することができる.Any は,任意のシリアライズされたメッセージを,グローバルにユニークな識別子として働きそのメッセージ型を解決する URL とともに,bytes として持つ.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 Any& detail : status.details()) {
  if (detail.Is<NetworkErrorDetails>()) {
    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}

現在,Any 型についてのランタイムライブラリは開発中である.

もし既に proto2 syntax に通じているならば,Any 型は extensions を置き換えることができる.

oneof

もしたくさんのフィールドを持つメッセージがあり,同時に多くても一つのフィールドのみがセットされるようならば,この振る舞いを oneof によって強制させメモリを節約することができる.

oneof のフィールドは,全てのフィールドがメモリを共有していて,また同時に多くても一つのフィールドのみがセットされること以外は,普通のフィールドと同じである.oneof のあるメンバをセットすると,自動的に他の全てのメンバはクリアされる.どの値がセットされているかは特別な case() 乃至 WhichOneof() メソッドを用いることで確認できる.

oneof を使う

.proto ファイル中で oneof を定義するためには名前の前に oneof キーワードをつける.次の例では test_oneof という oneof を定義している:

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

それから oneof のフィールドを oneof の定義に加えている.任意の型のフィールドを加えることができるが,repeated フィールドを使うことはできない.

生成されたコードでは,oneof のフィールドは普通のフィールドと同じゲッターとセッターを持つ.また oneof のどの値が(もしセットされているならば)セットされているかをチェックする特別なメソッドもある.oneof API についての詳細は API reference を参照されたい.

oneof の特徴

  • oneof のフィールドをセットすると自動的にその oneof の他の全てのフィールドがクリアされる.つまり,幾つかの oneof フィールドをセットすると,最後にセットしたフィールドのみが値を持つことになる.
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message();   // Will clear name field.
CHECK(!message.has_name());
  • もしパーサがワイヤ上で同じ oneof の複数のメンバに遭遇した場合,最後に目撃したメンバのみがパースされたメッセージに使われる.

  • oneof は repeated たりえない.

  • Reflection API は 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 の二つのメッセージを Swap() した場合,各メッセージはもう一方の oneof の状態となる.以下の例では,msg1sub_message を持ち,msg2name を持つことになる.
  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 を分割したりマージする: これは通常のフィールドを移動させる問題と似ている.

map

データ定義の一部として連想マップを使いたくなった場合,プロトコルバッファーは便利な短縮記法を用意している:

map<key_type, value_type> map_field = N;

ここで key_type は任意の整数もしくは文字列型(即ち,小数点型と bytes を除く任意のスカラー型)である.value_type は他のマップを除く任意の型である.

例えば,各 Project メッセージが文字列のキーに関連づいたプロジェクトのマップを作りたくなれば,次のように定義できる:

map<string, Project> projects = 3;
  • map フィールドは repeated たりえない.

  • map 値のワイヤフォーマット順番と map イテレーション順番は定義されていない,即ち map のアイテムが特定の順番になっていることに依拠してはならない.

  • .proto のテキストフォーマットが生成される時,map はキーによってソートされる.数値キーは数字によってソートされる.

  • ワイヤからパースする時やマージする時,もし重複するキーが存在すれば最後に目撃されたキーが用いられる.テキストフォーマットから map をパースする時,もし重複するキーが存在すればパースは失敗する可能性がある.

後方互換性

map シンタックスはワイヤ上では次に等しい.従って map をサポートしていないプロトコルバッファーの実装もあなたのデータを扱うことができる:

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++ 名前空間内でラップされる.例えば,Openfoo::bar 名前空間内に配置されるだろう.

  • Java では,.proto ファイルに明示的に option java_package を加えない限り,パッケージは Java パッケージとして用いられる.

  • Python では,パッケージ命令は無視される.Python モジュールはファイルシステム内での配置によって体系づけられるからである.

  • Go では,.proto ファイルに明示的に option go_package を加えない限り,パッケージは Go パッケージ名として使われる.

  • Ruby では,生成されたクラスはネストされた Ruby 名前空間内でラップされる.ただし,Ruby の要求する大文字化スタイルに変換される(最初の文字が大文字になる;もし最初の文字がレターでなければ PB_ が先頭に追加される).例えば,OpenFoo:Bar 名前空間内に配置されるだろう.

  • JavaNano では,.proto ファイルに明示的に option java_package を加えない限り,パッケージは Java パッケージとして用いられる.

  • C# では,.proto ファイルに明示的に option csharp_package を加えない限り,パッケージはパスカルケースに変換された後に名前空間として用いられる.例えば,OpenFoo.Bar 名前空間内に配置されるだろう.

パッケージと名前解決

プロトコルバッファー言語における型名解決は C++ のそれに似ている:まず最内のスコープが検索され,それから二番目に内側の,以後同様.それぞれのパッケージはその親パッケージに対して”内側にある”と見做される.先頭の .(例えば .foo.bar.Baz)は,代わりに最外のスコープからスタートすることを意味する.

プロトコルバッファーコンパイラはインポートされた .proto ファイルをパースすることで全ての型名を解決する.各言語のコード生成器は,たとえ異なるスコープルールを持っているとしても,その言語において各型をどのように参照するかを知っている.

サービス定義

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

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

プロトコルバッファーを用いた最も簡単な RPC システムは gRPC である.gRPC はプラットフォーム・ニュートラルでオープンソースの RPC システムであり,Google によって開発されている.gRPC はプロトコルバッファーを用いることで特によく働く.また特別なプロトコルバッファープラグインを用いることで .proto ファイルから直接関連する RPC コードを生成することができる.

もし gRPC を使いたくないならば,あなたの RPC 実装とともにプロトコルバッファーを用いることもできる.詳細については Proto2 Language Guide を参照のこと.

プロトコルバッファーに対する RPC 実装を開発しているたくさんのサードパーティがまた存在する.我々が知っているプロジェクトのリンクのリストについては third-party add-ons wiki page を見よ.

JSON マッピング

proto3 は標準的な JSON エンコードをサポートしている.これによりシステム間でデータを共有することが容易になる.エンコーディングはタイプ・バイ・タイプの基準で以下の表に示されている.

プロトコルバッファーへのパースに於いて,もし JSON エンコードされたデータ中である値が見つからなかったり null だった場合,適当なデフォルト値として解釈される.もしあるフィールドがプロトコルバッファーでデフォルト値を持っている場合,デフォルトでは空間の節約のために JSON エンコードされたデータではそれは省かれる.実装は,JSON エンコードされた出力に於いてデフォルト値を持つフィールドを省くオプションを提供するかもしれない.

proto3 JSON JSON example Notes
message object {"fBar": v, "g": null, …} JSON オブジェクトを生成する.メッセージフィールド名はローワーキャメルケースにマップされ JSON オブジェクトのキーとなる.null は受容可能で,対応するフィールド型のデフォルト値として扱われる.
enum string "FOO_BAR" proto で指定された enum 値の名前が使われる.
map object {"k": v, …} 全てのキーは文字列に変換される.
repeated V array [v, …] null は受容可能で,空リストとして扱われる.
bool true, false true, false
string string "Hello World!"
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+" JSON 値はパディング付き標準 base64 エンコーディングを用いて文字列にエンコードされたものになる.パディングあり / なし標準 or URL 安全 base64 エンコードの何れも受容可能.
int32, fixed32, uint32 number 1, -10, 0 JSON 値は小数になる.数値 or 文字列が受容可能.
int64, fixed64, uint64 string "1", "-10" JSON 値は小数の文字列になる.数値 or 文字列が受容可能.
float, double number 1.1, -10.0, 0, "NaN", "Infinity" JSON 値は数値か特別な値 "NaN", "Infinity", "-Infinity" の何れかとなる.数値 or 文字列が受容可能.指数表記も受容可能.
Any object {"@type": "url", "f": v, … } Any が特別な JSON マッピングを持つ値を含んでいる場合,それは次のように変換される:{"@type": xxx, "value": yyy }.それ以外では,値は JSON オブジェクトに変換され,"@type" フィールドが実際のデータ型を示すために挿入される.
Timestamp string "1972-01-01T10:00:20.021Z" RFC 3339 を用いる:出力は常に Z-正規化され 0, 3, 6, 9 の何れかの小数桁を用いる.
Duration string "1.000340012s", "1s" 出力は要求される精度に依存して常に 0, 3, 6, 9 の何れかの小数桁を含む.ナノ秒精度にフィットしている限り任意の小数桁 (もしくは none) を受容する.
Struct object { … } 任意の JSON オブジェクト.struct.proto を見よ.
Wrapper types various types 2, "2", "foo", true, "true", null, 0, … ラッパーはラップされたプリミティブ型と同じ JSON での表現を用いる.ただし null は許容され,データ変換と転送の間保持される.
FieldMask string "f.fooBar,h" fieldmask.proto を見よ.
ListValue array [foo, bar, …]
Value value 任意の JSON 値.
NullValue null JSON null

オプション

proto ファイル内の個々の宣言には幾つかのオプションによって注釈をつけることができる.オプションは定義の全体の意味は変化させないが,それがある文脈においてどのように扱われるかについて影響を与える.利用可能なオプションのリストは google/protobuf/descriptor.proto に定義されている.

幾つかのオプションはファイルレベルのオプションであり,それらはトップレベルスコープに書かれなければならない.即ち,メッセージや enum, サービス定義内に書いてはいけない.また幾つかのオプションはメッセージレベルのオプションであり,メッセージ定義の中に書かれなければならない.フィールドレベルのオプションはフィールド定義の中に書かれなければならない.オプションはまた enum 型,enum 値,サービス型,サービスメソッド上にも書かれうる.しかしながら,今の所これらについて有用なオプションはない.

以下は幾つかの最も一般的に使われるオプションである:

  • java_package(ファイルオプション):生成された Java クラスで用いたいパッケージ名.もし .proto ファイルに明示的に java_package オプションが与えられなければ,デフォルトで proto パッケージ(.proto ファイル中に "package" キーワードを用いることよって指定される)が使われる.しかし,proto パッケージは一般に良い Java パッケージを生成しない.これは,proto パッケージはリバースドメイン名で始まることを予期しないからである.もし Java コードを生成しない場合は,このオプションは何の効果もない.
  option java_package = "com.example.foo";
  • java_multiple_files(ファイルオプション):トップレベルメッセージ,enum, サービスを,.proto ファイルの名前がついた外側クラスの内側ではなく,パッケージレベルで定義されるようにする.
  option java_multiple_files = true;
  • java_outer_classname(ファイルオプション):生成したい最外の Java クラス(従ってそのファイル名)のクラス名.もし .proto ファイルに明示的に java_outer_classname オプションが与えられなければ,そのクラス名は .proto ファイル名をキャメルケースに変換したもの(即ち foo_bar.proto であれば FooBar.java)が使われる.もし Java コードを生成しない場合は,このオプションは何の効果もない.

  • optimize_for(ファイルオプション):SPPED, CODE_SIZE, LITE_RUNTIME の何れかに設定できる.これは C++ と Java コード生成器(もしくは他のサードパーティーによる生成器)に影響を与える.

    • SPEED(デフォルト):プロトコルバッファコンパイラはあなたのメッセージ型についてシリアライズ,パース,そして他の一般的な操作を行うためのコードを生成する.このコードは高度に最適化されている.
    • CODE_SIZE:プロトコルバッファコンパイラは最小のクラスを生成し,シリアライズ,パース,他の様々な操作については共有の,リフレクションベースのコードに依拠する.生成されたコードは従って SPEED の時よりかなり小さくなるが,操作は遅くなる.クラスはなお SPEED モードの時と全く同じ公開 API を実装する.このモードは非常に沢山の .proto ファイルを含み,それらすべてがむやみに速い必要がないようなアプリケーションに於いて最も便利である.
    • LITE_RUNTIME:プロトコルバッファコンパイラは”軽い”ランタイムライブラリのみに依存するクラスを生成する(即ち libprotobuf の代わりに libprotobuf-lite を用いる).軽いランタイムは完全なライブラリに比べるとかなり小さい(おおよそ桁レベルで小さい)が,デスクリプタやリフレクションのような特定の機能を省いている.これは特に携帯電話のような制限されたプラットフォームに於いて走るアプリケーションに於いて有用である.コンパイラはなお SPPED モードと同様の全てのメソッドの速い実装を生成する.各言語において,生成されたクラスは MessageLite インターフェースのみを実装し,これは完全な Message インターフェースの一部のみを提供する.
  option optimize_for = CODE_SIZE;
  • cc_enable_arenas(ファイルオプション):C++ 生成コードのための arena allocation を有効にする.

  • objc_class_prefix(ファイルオプション):この .proto から生成される全ての Objective-C クラスと enum に対して接頭される Objective-C クラスプリフィクスを設定する.デフォルト値はなし.Apple のリコメンドに従って 3-5 文字の大文字を接頭することが望ましい.ただし全ての 2 文字の接頭辞は Apple によって予約されている.

  • deprecated(フィールドオプション):これが true になっていることは,そのフィールドが非推奨であり新しいコードによって使われるべきでないということを意味する.殆どの言語では実際に何か影響を与えるわけではない.Java では,これは @Deprecated 注釈となる.将来,他の言語についてのコード生成器もフィールドアクセサに非推奨注釈を生成するようになるかもしれない.するとそのフィールドを使おうとしたコードをコンパイルした時,警告が発せられるようになるだろう.もしフィールドが誰にも使用されておらず,新しいユーザに使わせたくないのであれば,フィールド宣言に reserved を加えることを検討せよ.

  int32 old_field = 6 [deprecated=true];

カスタムオプション

プロトコルバッファーはまたあなた独自のオプションを定義したり使用することを許す.これは拡張機能であり殆どの人々にとっては必要ない.もし独自のオプションを作成したいのであれば Proto2 Language Guide を参照せよ.カスタムオプションの作成には extensions を使うが,これは proto3 ではカスタムオプションの作成に於いてのみ許可されていることに留意されたい.

クラスを生成する

Java, Python, C++, Go, Ruby, JavaNano, Objective-C, C# のコードを生成するためには,.proto ファイルでメッセージ型を定義し,プロトコルバッファーコンパイラ protoc にかける必要がある.もしコンパイラをまだインストールしていないのであれば,download the package で 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 --javanano_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 の省略形として使うことができる.

  • 一つ以上の出力指示を与えることができる.

    • --cpp_outDST_DIR 内に C++ コードを生成する.詳細は C++ generated code reference を参照.
    • --java_outDST_DIR 内に Java コードを生成する.詳細は Java generated code reference を参照.
    • --python_outDST_DIR 内に Python コードを生成する.詳細は Python generated code reference を参照.
    • --go_outDST_DIR 内に Go コードを生成する.詳細は Go generated code reference を参照.
    • --ruby_outDST_DIR 内に Ruby コードを生成する.リファレンスは近日公開!
    • --javanano_outDST_DIR 内に JavaNano コードを生成する.JavaNano コード生成器は出力をカスタマイズするための多くのオプションを持つ:詳細は README を参照のこと.リファレンスは近日公開!
    • --objc_outDST_DIR 内に Objective-C コードを生成する.詳細は Objective-C generated code reference を参照.
    • --csharp_outDST_DIR 内に C# コードを生成する.詳細は C# generated code reference を参照.
    • --php_outDST_DIR 内に PHP コードを生成する.詳細は PHP generated code reference を参照.

特別な便宜のために,もし DST_DIR.zip.jar で終わっているのであれば,コンパイラは出力を与えられた名前の一つの ZIP アーカイブファイルとする..jar 出力もまた Java JAR 仕様の要求するところに応じてマニフェストファイルが与えられる.ただし出力アーカイブが既に存在している場合は上書きされる;コンパイラは既存アーカイブにファイルを追加できるほど賢くはない.

  • 入力として一つ以上の .proto ファイルが与えられなければならない.複数の .proto ファイルを一度に指定することもできる.ファイルがカレントディレクトリからの相対名で与えられるとしても,コンパイラが正しい名前を決定できるように,各ファイルは IMPORT_PATH の中のどこかの場所にいなければならない.

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
151