このページは Protocol Buffers 公式リファレンス Go Generated Codeの和訳です。原文はCreative Commons Attribution 4.0 Licenseで公開されており、ソースコードはApache Licenseで公開されています。この訳文もそれにならいます。
Go Generated Code
このページでは、プロトコルバッファーコンパイラが、任意のプロトコル定義に対し。どのようなGoのコードを生成するかについて、正確に述べています。proto2とprot3の違いは明示されています(これらの違いはこのドキュメントに書かれているように生成されたコードにあり、ベースAPIにはありません。API上は二つのバージョンに違いはありません)。このドキュメントの前にproto2/proto3の言語仕様ガイドを読むことをお勧めします。
コンパイラ呼び出し
プロトコルバッファーコンパイラがGoのコードを生成するにはプラグインが必要です。 次のようにインストールすると、
$ go get github.com/golang/protobuf/protoc-gen-go
protoc
にコマンドラインフラグとして-go_out
を渡して実行した際に使われるprotoc-gen-go
のバイナリが入ります。--go_out
フラグで、コンパイラにGoのソースファイルを書き込む場所を指定します。 コンパイラは各.proto
ファイルの入力に対して、単一のソースファイルを作成します。
出力されるファイル名は.protoファイルの名前と以下の二つの変更によって決定されます。
-
.proto
拡張子は.pb.go
に置き換えられます。例えば、player_record.proto
というファイルはplayer_record.pb.go
という名前になります。 - (
--proto_path
、もしくは-I
で指定された)protoパスは、(--go_out
で指定された)ソース出力先に置き換えられます。
次のようにプロトコンパイラを実行すると
protoc --proto_path=src --go_out=build/gen src/foo.proto src/bar/baz.proto
コンパイラはsrc/foo.proto
とsrc/bar/baz.proto
から、build/gen/foo.pb.go
とbuild/gen/bar/baz.pb.go
を生成します。
必要であれば、コンパイラはbuild/gen/bar
ディレクトリを自動生成しますが、build
やbuild/gen
は生成しません。これらは予め作成しておく必要があります。
パッケージ
.proto
ファイルがパッケージ宣言を含んでいる場合、生成されたコードはprotoにあるpackage
をGoのパッケージ名として使います。その際まず.
を_
に変換します。例えば、protoのパッケージ名がexample.high_score
であれば、Goのパッケージ名はexample_high_score
になります。
.proto
ファイル内にgo_package
optionを入れることで、特定の.proto
に生成されるデフォルトのパッケージ名を上書きすることができます。例えば、.proto
ファイルに次のように書かれているとすると、
package example.high_score;
option go_package = "hs";
hs
というGoのパッケージ名が生成されます。
他にありうるケースとして、.proto
ファイルにパッケージ宣言が含まれていない場合、生成されたコードはファイル名(から拡張子を除いたもの)をGoのパッケージ名にします。その際、まず.
は_
に変換されます。例えば、パッケージ宣言のないhigh.score.proto
という名前のprotoパッケージは、high.score.pb.go
というファイルのhigh_score
というパッケージで出力されます。
メッセージ型
次のようなシンプルなメッセージ宣言があるとします。
message Foo {}
プロトコルバッファコンパイラはFoo
という構造体を生成します。*Foo
はMessageインターフェースを実装します。詳細についてはインラインコメントで説明します。
type Foo struct {
}
// Reset はprotoの状態をデフォルト値にします。
func (m *Foo) Reset() { *m = Foo{} }
// String は、protoを文字列表現にして返します。
func (m *Foo) String() string { return proto.CompactTextString(m) }
// ProtoMessage はprotoのMessageであることを明示的に示すために付加されており、
// 誰かが誤ってproto.Messageインターフェースを実装しないようにします
func (*Foo) ProtoMessage() {}
これらのメンバメソッドは常に存在します。また、optimize_for
オプションをつけても、Goでは生成コードは変わりません(訳注:Java/C++では最適化されます)。
ネストされた型
メッセージ型は他のメッセージ型の中に宣言することもできます。例を示します。
message Foo {
message Bar {
}
}
この場合、コンパイラは二つの構造体を生成します。Foo
とFoo_Bar
です。
Well-known types
プロトコルバッファには、 well-known types (WKT)と呼ばれる、一連の定義済みメッセージが付属しています。これの型は、他のサービスとの相互運用性のためにも、あるいは単純によくある有用なパターンを簡潔に表現するためにも使えます。例えば、Structメッセージ型は任意のC言語風の構造体を表します。
WKT用に事前生成されたGoのコードが、Go Protobuf ライブラリ の一部として配布されており、WKTを使うと、あなたが定義したメッセージ型から生成されたコードからも参照されます。例えば、このようにメッセージ型を定義したとします。
import "google/protobuf/struct.proto"
import "google/protobuf/timestamp.proto"
message NamedStruct {
string name = 1;
google.protobuf.Struct definition = 2;
google.protobuf.Timestamp last_modified = 3;
}
生成されるGoのコードは次のようになるでしょう。
import google_protobuf "github.com/golang/protobuf/ptypes/struct"
import google_protobuf1 "github.com/golang/protobuf/ptypes/timestamp"
...
type NamedStruct struct {
Name string
Definition *google_protobuf.Struct
LastModified *google_protobuf1.Timestamp
}
大抵の場合、コード内でこれらの型を直接インポートする必要はないでしょう。ただ、もし直接これらの型を参照する必要があれば、単純にgithub.com/golang/protobuf/ptypes/[TYPE]
パッケージをインポートして、普通に使うだけです。
フィールド
プロトコルバッファコンパイラはメッセージ型の中に定義されたそれぞれのフィールドに対応する構造体のフィールドを生成します。その性質が実際にどのようになるかは、フィールドの型や単数、配列、マップ、oneofのうちの何であるかによって異なります。
Goのフィールド名は必ずキャメルケースで生成されます。.proto
ファイル内でフィールド名がこのようにスネークケースでで記述されていても同じです。大文字と小文字の変換は次のような流れで機能します。
-
先頭文字はエクスポートするため大文字になります。先頭文字がアンダースコアの場合は削除され、代わりに大文字の
X
が前置されます。 -
先頭文字以外でアンダースコアが小文字に続く場合、アンダースコアは削除され、それに続く小文字は大文字に変換されます。
したがって、foo_bar_baz
というprotoのフィールドはFooBarBaz
となり、_my_field_name_2
はXMyFieldName_2
となります。
単数スカラー値フィールド (proto2)
下のフィールド定義のどちらの場合にも
optional int32 foo = 1;
required int32 foo = 1;
コンパイラはFoo
という名前の*int32
型のフィールドと、GetFoo()
というメソッドを持った構造体を生成します。GetFoo()
はint32
値か、フィールドに値が設定されていない場合はデフォルト値を返します。デフォルト値が明示的に設定されていない場合は、その型のゼロ値を返します(数値であれば0、文字列であれば空文字になります)。
(bool``bytes``string
といった)他のスカラー型のフィールドでは、上記の*int32
の部分はスカラー値型テーブル上で対応するGoの型に置き換えられます。
単数形スカラー値フィールド (proto3)
このフィールド定義の場合
int32 foo = 1;
コンパイラはFoo
という名前の*int32
型のフィールドと、GetFoo()
というメソッドを持った構造体を生成します。GetFoo()
はint32
値か、フィールドに値が設定されていない場合はデフォルト値を返します。デフォルト値が明示的に設定されていない場合は、その型のゼロ値を返します(数値であれば0、文字列であれば空文字になります)。
(bool``bytes``string
といった)他のスカラー型のフィールドでは、上記の*int32
の部分はスカラー値型テーブル上で対応するGoの型に置き換えられます。proto内で未設定の値は、その型のゼロ値として表現されます(数値であれば0、文字列であれば空文字になります)。
単数形メッセージ型フィールド
あるメッセージ型が定義され、
message Bar {}
Bar
フィールドを持ったメッセージ型に対しては、
// proto2
message Baz {
optional Bar foo = 1;
// optionalでなくrequiredでも生成コードは同じ
}
// proto3
message Baz {
Bar foo = 1;
}
コンパイラは次のようなGoの構造体を生成します。
type Baz struct {
Foo *Bar
}
メッセージ型フィールドはnil
の場合があります。この場合フィールドは設定されておらず、実際は空の値です。これは空の
メッセージ型となる構造体の空のインスタンスを値に設定した場合とは異なります。
またコンパイラはヘルパー関数としてfunc (m *Baz) GetFoo() *Bar
を生成します。このおかげで、途中のnil
チェックなしに参照呼び出しを連鎖させることができます。
repeatedフィールド
各repeated
フィールドは、Goの構造体内の該当するフィールドの要素型がT
であるとき、フィールドにT
のスライスを生成します。
repeated
フィールドを持ったこのメッセージの場合、
message Baz {
repeated Bar foo = 1;
}
コンパイラは以下のような構造体を生成します。
type Baz struct {
Foo []*Bar
}
同様にフィールド定義が、repeated bytes foo = 1;
であった場合、コンパイラはFoo
という名前の[][]byte
フィールドを持ったGoの構造体を生成します。repeated enumの場合、repeated MyEnum bar = 2;
に対して、コンパイラはBar
という名前の[]MyEnum
フィールドを持った構造体を生成します。
mapフィールド
各mapフィールドにおいて、TKey
がフィールドのキーとなる型で、TValue
がフィールドの値となる型である場合、コンパイラはGoの構造体に、map[TKey]TValue
型のフィールドを生成します。
map
フィールドを持ったこのメッセージの場合、
message Bar {}
message Baz {
map<string, Bar> foo = 1;
}
コンパイラは次のようなGo構造体を生成します。
type Baz struct {
Foo map[string]*Bar
}
oneof フィールド
oneof フィールドの場合、プロトコルバッファコンパイラはisMessageName_MyField
というインターフェースのフィールドをひとつ生成します。またoneof
の中のそれぞれの単数フィールドに相当する構造体を生成します。これらは全てこのisMessageName_MyField
インターフェースを実装します。
oneofフィールドを持ったこのメッセージの場合、
package account;
message Profile {
oneof avatar {
string image_url = 1;
bytes image_data = 2;
}
}
コンパイラはt次の構造体を生成します。
type Profile struct {
// Avatarに代入できる型:
// *Profile_ImageUrl
// *Profile_ImageData
Avatar isProfile_Avatar `protobuf_oneof:"avatar"`
}
type Profile_ImageUrl struct {
ImageUrl string
}
type Profile_ImageData struct {
ImageData []byte
}
*Profile_ImageUrl
と*Profile_ImageData
のどちらも、空のisProfile_Avatar()
メソッドを提供することで、isProfile_Avatar
を満たしています。
次の例でどのようにしてフィールドに値を入れるかを示します。
p1 := &account.Profile{
Avatar: &account.Profile_ImageUrl{"http://example.com/image.png"},
}
// imageDataは[]byteです
imageData := getImageData()
p2 := &account.Profile{
Avatar: &account.Profile_ImageData{imageData},
}
フィールドにアクセスするには、値の型によるswitch文を使うことで、メッセージ型ごとに処理することができます。
switch x := m.Avatar.(type) {
case *account.Profile_ImageUrl:
// x.ImageUrlを使ってURLからくProfile画像を読み込む
case *account.Profile_ImageData:
// x.ImageDataを使ってbyte配列からProfile画像を読み込む
case nil:
// フィールドがない
default:
return fmt.Errorf("Profile.Avatarに未知の型が割り当てられています %T", x)
}
またコンパイラはfunc (m *Profile) GetImageUrl() string
とfunc (m *Profile) GetImageData() []byte
というメソッドも生成します。それぞれのget関数はフィールドの値か、もし未設定であればゼロ値を返します。
列挙型
次のような列挙型が与えられた場合、
message SearchRequest {
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 1;
...
}
プロトコルバッファコンパイラは型とその型の一連の定数を生成します。
(上記のような)メッセージ型の中に列挙型がある場合、型の名前はメッセージ型の名前から始まります。
type SearchRequest_Corpus int32
パッケージレベルの列挙型の場合、
enum Foo {
DEFAULT_BAR = 0;
BAR_BELLS = 1;
BAR_B_CUE = 2;
}
Goの型の名前はprotoのenumの名前と同じです。
type Foo int32
この型にはその値のラベルを返すString()
メソッドがあります。
Enum()
メソッド1は、その値に割り当てられたメモリを初期化し、対応するポインタを返します。
func (Foo) Enum() *Foo
プロトコルバッファコンパイラはそれぞれの列挙型の値に対して定数を生成します。メッセージ内に列挙型がある場合、定数名の頭に親のメッセージ型の名が付きます。
const (
SearchRequest_UNIVERSAL SearchRequest_Corpus = 0
SearchRequest_WEB SearchRequest_Corpus = 1
SearchRequest_IMAGES SearchRequest_Corpus = 2
SearchRequest_LOCAL SearchRequest_Corpus = 3
SearchRequest_NEWS SearchRequest_Corpus = 4
SearchRequest_PRODUCTS SearchRequest_Corpus = 5
SearchRequest_VIDEO SearchRequest_Corpus = 6
)
パッケージレベルの列挙型では、定数は代わりに列挙型の名前から始まります。
const (
Foo_DEFAULT_BAR Foo = 0
Foo_BAR_BELLS Foo = 1
Foo_BAR_B_CUE Foo = 2
)
プロトコルバッファコンパイラは、整数値をキーにラベルの文字列の名前を値にしたmapと、ラベルの文字列をキーに整数値を値にしたmapを生成します。
var Foo_name = map[int32]string{
0: "DEFAULT_BAR",
1: "BAR_BELLS",
2: "BAR_B_CUE",
}
var Foo_value = map[string]int32{
"DEFAULT_BAR": 0,
"BAR_BELLS": 1,
"BAR_B_CUE": 2,
}
.protoの記法では、複数の列挙型のラベルが同じ数の値を持つことができます。同じ値を持つラベルは同義語です。Goではこれらはきっかり同じように表現されます。すなわち同じ数値に複数の名前が対応するようになります。数値からラベルを逆引きするmapでは.protoで最初に現れるラベルだけが使われます。
拡張機能 (proto2)
拡張機能はproto2にのみ存在します。 proto2拡張のGo生成コードAPIのドキュメントについては、protoパッケージのドキュメントを参照してください。
Service
GoのコードジェネレータはデフォルトではServiceを生成しません。 gRPCプラグインを有効にすると(gRPC Goクイックスタートガイドを参照)、gRPCをサポートするコードが生成されます。
-
v1.3.2現在、列挙型で生成されるコードにEnum()メソッドが存在しない ↩