これはなに
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.h
とpbobjc.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
フィールドを追加したいとする.corpus
は UNIVERSAL
, 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 ビットに切り詰められる). -
sint32
とsint64
は互換だが,他の整数型とは互換でない. -
stirng
とbytes
は,bytes が有効な UTF-8 である場合に限り互換である. -
埋め込まれたメッセージは,もし bytes がそのメッセージのエンコードされたバージョンを含んでいれば,
bytes
と互換である. -
fixed32
はsfixed32
と互換である.また,fixed64
とsfixed64
も互換である. -
enum
はint32
,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 の状態となる.以下の例では,`msg1` は `sub_message` を持ち,`msg2` は `name` を持つことになる.
```protobuf
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++ 名前空間内でラップされる.例えば,
Open
はfoo::bar
名前空間内に配置されるだろう. -
Java では,
.proto
ファイルに明示的にoption java_package
を加えない限り,パッケージは Java パッケージとして用いられる. -
Python では,パッケージ命令は無視される.Python モジュールはファイルシステム内での配置によって体系づけられるからである.
-
Go では,
.proto
ファイルに明示的にoption go_package
を加えない限り,パッケージは Go パッケージ名として使われる. -
Ruby では,生成されたクラスはネストされた Ruby 名前空間内でラップされる.ただし,Ruby の要求する大文字化スタイルに変換される(最初の文字が大文字になる;もし最初の文字がレターでなければ
PB_
が先頭に追加される).例えば,Open
はFoo:Bar
名前空間内に配置されるだろう. -
JavaNano では,
.proto
ファイルに明示的にoption java_package
を加えない限り,パッケージは Java パッケージとして用いられる. -
C# では,
.proto
ファイルに明示的にoption csharp_package
を加えない限り,パッケージはパスカルケースに変換された後に名前空間として用いられる.例えば,Open
はFoo.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_out
はDST_DIR
内に C++ コードを生成する.詳細は C++ generated code reference を参照. -
--java_out
はDST_DIR
内に Java コードを生成する.詳細は Java generated code reference を参照. -
--python_out
はDST_DIR
内に Python コードを生成する.詳細は Python generated code reference を参照. -
--go_out
はDST_DIR
内に Go コードを生成する.詳細は Go generated code reference を参照. -
--ruby_out
はDST_DIR
内に Ruby コードを生成する.リファレンスは近日公開! -
--javanano_out
はDST_DIR
内に JavaNano コードを生成する.JavaNano コード生成器は出力をカスタマイズするための多くのオプションを持つ:詳細は README を参照のこと.リファレンスは近日公開! -
--objc_out
はDST_DIR
内に Objective-C コードを生成する.詳細は Objective-C generated code reference を参照. -
--csharp_out
はDST_DIR
内に C# コードを生成する.詳細は C# generated code reference を参照. -
--php_out
はDST_DIR
内に PHP コードを生成する.詳細は PHP generated code reference を参照.
-
特別な便宜のために,もし DST_DIR
が .zip
や .jar
で終わっているのであれば,コンパイラは出力を与えられた名前の一つの ZIP アーカイブファイルとする..jar
出力もまた Java JAR 仕様の要求するところに応じてマニフェストファイルが与えられる.ただし出力アーカイブが既に存在している場合は上書きされる;コンパイラは既存アーカイブにファイルを追加できるほど賢くはない.
- 入力として一つ以上の
.proto
ファイルが与えられなければならない.複数の.proto
ファイルを一度に指定することもできる.ファイルがカレントディレクトリからの相対名で与えられるとしても,コンパイラが正しい名前を決定できるように,各ファイルはIMPORT_PATH
の中のどこかの場所にいなければならない.