LoginSignup
2
1

More than 3 years have passed since last update.

Protocol Buffers / Go Generated Code(和訳)

Posted at

このページは 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.protosrc/bar/baz.protoから、build/gen/foo.pb.gobuild/gen/bar/baz.pb.goを生成します。

必要であれば、コンパイラはbuild/gen/barディレクトリを自動生成しますが、buildbuild/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という構造体を生成します。*FooMessageインターフェースを実装します。詳細についてはインラインコメントで説明します。

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

この場合、コンパイラは二つの構造体を生成します。FooFoo_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ファイル内でフィールド名がこのようにスネークケースでで記述されていても同じです。大文字と小文字の変換は次のような流れで機能します。

  1. 先頭文字はエクスポートするため大文字になります。先頭文字がアンダースコアの場合は削除され、代わりに大文字のXが前置されます。

  2. 先頭文字以外でアンダースコアが小文字に続く場合、アンダースコアは削除され、それに続く小文字は大文字に変換されます。

したがって、foo_bar_bazというprotoのフィールドはFooBarBazとなり、_my_field_name_2XMyFieldName_2となります。

単数スカラー値フィールド (proto2)

下のフィールド定義のどちらの場合にも

optional int32 foo = 1;
required int32 foo = 1;

コンパイラはFooという名前の*int32型のフィールドと、GetFoo()というメソッドを持った構造体を生成します。GetFoo()int32値か、フィールドに値が設定されていない場合はデフォルト値を返します。デフォルト値が明示的に設定されていない場合は、その型のゼロ値を返します(数値であれば0、文字列であれば空文字になります)。

(boolbytesstringといった)他のスカラー型のフィールドでは、上記の*int32の部分はスカラー値型テーブル上で対応するGoの型に置き換えられます。

単数形スカラー値フィールド (proto3)

このフィールド定義の場合

int32 foo = 1;

コンパイラはFooという名前の*int32型のフィールドと、GetFoo()というメソッドを持った構造体を生成します。GetFoo()int32値か、フィールドに値が設定されていない場合はデフォルト値を返します。デフォルト値が明示的に設定されていない場合は、その型のゼロ値を返します(数値であれば0、文字列であれば空文字になります)。

(boolbytesstringといった)他のスカラー型のフィールドでは、上記の*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() stringfunc (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をサポートするコードが生成されます。


  1. v1.3.2現在、列挙型で生成されるコードにEnum()メソッドが存在しない 

2
1
0

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
2
1