Go生成コードガイド (Open)
proto2、proto3、およびeditionsで生成されるコードの違いは強調されています。これらの違いは、このドキュメントで説明されている生成コードにあり、両方のバージョンで同じである基本APIにはないことに注意してください。このドキュメントを読む前に、proto2言語ガイド、proto3言語ガイド、またはeditions言語ガイドを読む必要があります。
注
あなたは古い生成コードAPI(Open Struct API)のドキュメンテーションを見ています。(新しい)Opaque APIの対応するドキュメンテーションについては、Go生成コード (Opaque) を参照してください。Opaque APIの紹介については、Go Protobuf: The new Opaque APIを参照してください。コンパイラの呼び出し
protocol bufferコンパイラは、Goコードを生成するためにプラグインが必要です。Go 1.16以降を使用して、以下を実行してインストールします。
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
これにより、protoc-gen-go
バイナリが$GOBIN
にインストールされます。インストール場所を変更するには、$GOBIN
環境変数を設定します。protocol bufferコンパイラがそれを見つけられるように、$PATH
に含まれている必要があります。
protocol bufferコンパイラは、go_out
フラグを付けて呼び出されるとGoの出力を生成します。go_out
フラグの引数は、コンパイラにGoの出力を書き込ませたいディレクトリです。コンパイラは、入力.proto
ファイルごとに1つのソースファイルを作成します。出力ファイルの名前は、.proto
拡張子を.pb.go
に置き換えることによって作成されます。
生成された.pb.go
ファイルが出力ディレクトリのどこに配置されるかは、コンパイラのフラグによって異なります。いくつかの出力モードがあります。
paths=import
フラグが指定されている場合、出力ファイルはGoパッケージのインポートパス(.proto
ファイル内のgo_package
オプションで提供されるものなど)にちなんで名付けられたディレクトリに配置されます。たとえば、Goインポートパスがexample.com/project/protos/fizz
である入力ファイルprotos/buzz.proto
は、example.com/project/protos/fizz/buzz.pb.go
に出力ファイルを生成します。これは、paths
フラグが指定されていない場合のデフォルトの出力モードです。module=$PREFIX
フラグが指定されている場合、出力ファイルはGoパッケージのインポートパス(.proto
ファイル内のgo_package
オプションで提供されるものなど)にちなんで名付けられたディレクトリに配置されますが、指定されたディレクトリプレフィックスが出力ファイル名から削除されます。たとえば、Goインポートパスがexample.com/project/protos/fizz
で、module
プレフィックスとしてexample.com/project
が指定されている入力ファイルprotos/buzz.proto
は、protos/fizz/buzz.pb.go
に出力ファイルを生成します。モジュールパス外のGoパッケージを生成するとエラーになります。このモードは、生成されたファイルをGoモジュールに直接出力する場合に便利です。paths=source_relative
フラグが指定されている場合、出力ファイルは入力ファイルと同じ相対ディレクトリに配置されます。たとえば、入力ファイルprotos/buzz.proto
は、protos/buzz.pb.go
に出力ファイルを生成します。
protoc-gen-go
に固有のフラグは、protoc
を呼び出すときにgo_opt
フラグを渡すことによって提供されます。複数のgo_opt
フラグを渡すことができます。たとえば、次を実行する場合、
protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto
コンパイラはsrc
ディレクトリ内の入力ファイルfoo.proto
とbar/baz.proto
を読み取り、出力ファイルfoo.pb.go
とbar/baz.pb.go
をout
ディレクトリに書き込みます。コンパイラは必要に応じてネストされた出力サブディレクトリを自動的に作成しますが、出力ディレクトリ自体は作成しません。
パッケージ
Goコードを生成するためには、すべての.proto
ファイル(生成される.proto
ファイルによって推移的に依存されているものを含む)に対してGoパッケージのインポートパスを提供する必要があります。Goのインポートパスを指定するには2つの方法があります。
.proto
ファイル内で宣言する方法、またはprotoc
を呼び出すときにコマンドラインで宣言する方法です。
.proto
ファイルのGoパッケージを.proto
ファイル自体で一元的に識別でき、protoc
を呼び出すときに渡すフラグのセットを簡素化できるため、.proto
ファイル内で宣言することをお勧めします。特定の.proto
ファイルのGoインポートパスが.proto
ファイル自体とコマンドラインの両方で提供される場合、後者が前者に優先します。
Goのインポートパスは、Goパッケージの完全なインポートパスを持つgo_package
オプションを宣言することによって、.proto
ファイル内でローカルに指定されます。使用例:
option go_package = "example.com/project/protos/fizz";
Goのインポートパスは、コンパイラを呼び出すときにコマンドラインで指定できます。それには、1つ以上のM${PROTO_FILE}=${GO_IMPORT_PATH}
フラグを渡します。使用例:
protoc --proto_path=src \
--go_opt=Mprotos/buzz.proto=example.com/project/protos/fizz \
--go_opt=Mprotos/bar.proto=example.com/project/protos/foo \
protos/buzz.proto protos/bar.proto
すべての.proto
ファイルからそれらのGoインポートパスへのマッピングは非常に大きくなる可能性があるため、このGoインポートパスを指定するモードは、通常、依存関係ツリー全体を制御する何らかのビルドツール(例:Bazel)によって実行されます。特定の.proto
ファイルに重複したエントリがある場合、最後に指定されたものが優先されます。
go_package
オプションとM
フラグの両方で、値にはセミコロンでインポートパスから区切られた明示的なパッケージ名を含めることができます。例:"example.com/protos/foo;package_name"
。パッケージ名はデフォルトでインポートパスから適切な方法で導出されるため、この使用法は推奨されません。
インポートパスは、ある.proto
ファイルが別の.proto
ファイルをインポートするときに、どのインポート文を生成する必要があるかを決定するために使用されます。たとえば、a.proto
がb.proto
をインポートする場合、生成されたa.pb.go
ファイルは、生成されたb.pb.go
ファイルを含むGoパッケージをインポートする必要があります(両方のファイルが同じパッケージにある場合を除く)。インポートパスは、出力ファイル名を構築するためにも使用されます。詳細については、上記の「コンパイラの呼び出し」セクションを参照してください。
Goのインポートパスと.proto
ファイルのpackage
指定子との間には相関関係はありません。後者はprotobufの名前空間にのみ関連し、前者はGoの名前空間にのみ関連します。また、Goのインポートパスと.proto
のインポートパスとの間にも相関関係はありません。
APIレベル
生成されたコードは、Open Struct APIまたはOpaque APIのいずれかを使用します。導入については、Go Protobuf: The new Opaque APIブログ投稿を参照してください。
.proto
ファイルが使用する構文に応じて、使用されるAPIは次のようになります。
.proto 構文 | APIレベル |
---|---|
proto2 | Open Struct API |
proto3 | Open Struct API |
edition 2023 | Open Struct API |
edition 2024+ | Opaque API |
APIは、.proto
ファイルでapi_level
editions feature を設定することで選択できます。これはファイルごと、またはメッセージごとに設定できます。
edition = "2023";
package log;
import "google/protobuf/go_features.proto";
option features.(pb.go).api_level = API_OPAQUE;
message LogEntry { … }
便宜上、protoc
コマンドラインフラグでデフォルトのAPIレベルをオーバーライドすることもできます。
protoc […] --go_opt=default_api_level=API_HYBRID
特定のファイル(すべてのファイルではなく)のデフォルトAPIレベルをオーバーライドするには、apilevelM
マッピングフラグを使用します(インポートパスのM
フラグと同様)。
protoc […] --go_opt=apilevelMhello.proto=API_HYBRID
コマンドラインフラグは、まだproto2またはproto3構文を使用している.proto
ファイルでも機能しますが、.proto
ファイル内からAPIレベルを選択したい場合は、まずそのファイルをeditionsに移行する必要があります。
メッセージ
単純なメッセージ宣言を考えます。
message Artist {}
protocol bufferコンパイラはArtist
という名前のstructを生成します。*Artist
はproto.Message
インターフェースを実装します。
proto
パッケージは、バイナリ形式との変換を含む、メッセージを操作する関数を提供します。
proto.Message
インターフェースはProtoReflect
メソッドを定義します。このメソッドは、メッセージのリフレクションベースのビューを提供するprotoreflect.Message
を返します。
optimize_for
オプションは、Goコードジェネレータの出力には影響しません。
複数のゴルーチンが同じメッセージに同時にアクセスする場合、次のルールが適用されます。
- フィールドへの同時アクセス(読み取り)は、1つの例外を除いて安全です。
- 遅延フィールドに初めてアクセスすることは変更です。
- 同じメッセージ内の異なるフィールドを同時に変更することは安全です。
- フィールドを同時に変更することは安全ではありません。
proto.Marshal
やproto.Size
などのproto
パッケージの関数と同時にメッセージを何らかの方法で変更することは安全ではありません。
ネストされた型
メッセージは別のメッセージ内で宣言できます。例:
message Artist {
message Name {
}
}
この場合、コンパイラはArtist
とArtist_Name
の2つのstructを生成します。
フィールド
protocol bufferコンパイラは、メッセージ内で定義された各フィールドに対してstructフィールドを生成します。このフィールドの正確な性質は、その型と、それが単数、繰り返し、マップ、またはoneofフィールドであるかどうかによって異なります。
.proto
ファイルのフィールド名がアンダースコア付きの小文字(そうあるべき)であっても、生成されるGoのフィールド名は常にキャメルケースを使用することに注意してください。大文字と小文字の変換は次のように機能します。
- エクスポートするために、最初の文字は大文字になります。最初の文字がアンダースコアの場合、それは削除され、大文字のXが先頭に追加されます。
- 内側のアンダースコアの後に小文字が続く場合、アンダースコアは削除され、続く文字が大文字になります。
したがって、protoフィールドbirth_year
はGoではBirthYear
になり、_birth_year_2
はXBirthYear_2
になります。
明示的なプレゼンスを持つ単数スカラーフィールド
フィールド定義について:
int32 birth_year = 1;
コンパイラは、BirthYear
という名前の*int32
フィールドを持つstructと、Artist
内のint32
値を返すか、フィールドが設定されていない場合はデフォルト値を返すアクセサメソッドGetBirthYear()
を生成します。デフォルトが明示的に設定されていない場合は、その型のゼロ値が代わりに使用されます(数値の場合は0
、文字列の場合は空文字列)。
他のスカラーフィールド型(bool
、bytes
、string
を含む)の場合、*int32
はスカラー値型テーブルに従って対応するGoの型に置き換えられます。
暗黙的なプレゼンスを持つ単数スカラーフィールド
このフィールド定義について:
int32 birth_year = 1;
コンパイラは、BirthYear
という名前のint32
フィールドを持つstructと、birth_year
内のint32
値を返すか、フィールドが設定されていない場合はその型のゼロ値(数値の場合は0
、文字列の場合は空文字列)を返すアクセサメソッドGetBirthYear()
を生成します。
FirstActiveYear
structフィールドは、optional
とマークされているため、型が*int32
になります。
他のスカラーフィールド型(bool
、bytes
、string
を含む)の場合、int32
はスカラー値型テーブルに従って対応するGoの型に置き換えられます。protoで設定されていない値は、その型のゼロ値(数値の場合は0
、文字列の場合は空文字列)として表現されます。
単数メッセージフィールド
メッセージ型を考えると:
message Band {}
Band
フィールドを持つメッセージの場合:
// proto2
message Concert {
optional Band headliner = 1;
// The generated code is the same result if required instead of optional.
}
// proto3
message Concert {
Band headliner = 1;
}
// editions
message Concer {
Band headliner = 1;
}
コンパイラはGoのstructを生成します。
type Concert struct {
Headliner *Band
}
メッセージフィールドはnil
に設定できます。これはフィールドが設定されていないことを意味し、事実上フィールドをクリアします。これは、値をメッセージstructの「空の」インスタンスに設定することとは異なります。
コンパイラはまた、ヘルパー関数func (m *Concert) GetHeadliner() *Band
を生成します。この関数は、m
がnilであるか、headliner
が設定されていない場合にnil
の*Band
を返します。これにより、中間のnil
チェックなしでget呼び出しを連鎖させることが可能になります。
var m *Concert // defaults to nil
log.Infof("GetFoundingYear() = %d (no panic!)", m.GetHeadliner().GetFoundingYear())
繰り返しフィールド
各repeatedフィールドは、GoのstructにT
のスライスフィールドを生成します。ここで、T
はフィールドの要素型です。repeatedフィールドを持つこのメッセージの場合:
message Concert {
// Best practice: use pluralized names for repeated fields:
// /programming-guides/style#repeated-fields
repeated Band support_acts = 1;
}
コンパイラはGoのstructを生成します。
type Concert struct {
SupportActs []*Band
}
同様に、フィールド定義repeated bytes band_promo_images = 1;
の場合、コンパイラはBandPromoImage
という名前の[][]byte
フィールドを持つGoのstructを生成します。repeated MusicGenre genres = 2;
のようなrepeatedの列挙型の場合、コンパイラはGenre
という名前の[]MusicGenre
フィールドを持つstructを生成します。
次の例は、フィールドを設定する方法を示しています。
concert := &Concert{
SupportActs: []*Band{
{}, // First element.
{}, // Second element.
},
}
フィールドにアクセスするには、次のようにします。
support := concert.GetSupportActs() // support type is []*Band.
b1 := support[0] // b1 type is *Band, the first element in support_acts.
マップフィールド
各mapフィールドは、structにmap[TKey]TValue
型のフィールドを生成します。ここでTKey
はフィールドのキー型、TValue
はフィールドの値型です。mapフィールドを持つこのメッセージの場合:
message MerchItem {}
message MerchBooth {
// items maps from merchandise item name ("Signed T-Shirt") to
// a MerchItem message with more details about the item.
map<string, MerchItem> items = 1;
}
コンパイラはGoのstructを生成します。
type MerchBooth struct {
Items map[string]*MerchItem
}
Oneof フィールド
oneofフィールドの場合、protobufコンパイラはインターフェース型isMessageName_MyField
を持つ単一のフィールドを生成します。また、oneof内の単数フィールドごとにstructを生成します。これらはすべて、このisMessageName_MyField
インターフェースを実装します。
oneofフィールドを持つこのメッセージの場合:
package account;
message Profile {
oneof avatar {
string image_url = 1;
bytes image_data = 2;
}
}
コンパイラはstructを生成します。
type Profile struct {
// Types that are valid to be assigned to 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{ImageUrl: "http://example.com/image.png"},
}
// imageData is []byte
imageData := getImageData()
p2 := &account.Profile{
Avatar: &account.Profile_ImageData{ImageData: imageData},
}
フィールドにアクセスするには、値に対して型スイッチを使用して、さまざまなメッセージ型を処理できます。
switch x := m.Avatar.(type) {
case *account.Profile_ImageUrl:
// Load profile image based on URL
// using x.ImageUrl
case *account.Profile_ImageData:
// Load profile image based on bytes
// using x.ImageData
case nil:
// The field is not set.
default:
return fmt.Errorf("Profile.Avatar has unexpected type %T", x)
}
コンパイラはまた、getメソッドfunc (m *Profile) GetImageUrl() string
とfunc (m *Profile) GetImageData() []byte
を生成します。各get関数は、そのフィールドの値を返すか、設定されていない場合はゼロ値を返します。
列挙型
次のような列挙型が与えられた場合:
message Venue {
enum Kind {
KIND_UNSPECIFIED = 0;
KIND_CONCERT_HALL = 1;
KIND_STADIUM = 2;
KIND_BAR = 3;
KIND_OPEN_AIR_FESTIVAL = 4;
}
Kind kind = 1;
// ...
}
protocol bufferコンパイラは、型とその型の一連の定数を生成します。
type Venue_Kind int32
const (
Venue_KIND_UNSPECIFIED Venue_Kind = 0
Venue_KIND_CONCERT_HALL Venue_Kind = 1
Venue_KIND_STADIUM Venue_Kind = 2
Venue_KIND_BAR Venue_Kind = 3
Venue_KIND_OPEN_AIR_FESTIVAL Venue_Kind = 4
)
メッセージ内のenum(上記のような)の場合、型名はメッセージ名で始まります。
type Venue_Kind int32
パッケージレベルのenumの場合:
enum Genre {
GENRE_UNSPECIFIED = 0;
GENRE_ROCK = 1;
GENRE_INDIE = 2;
GENRE_DRUM_AND_BASS = 3;
// ...
}
Goの型名はprotoのenum名から変更されません。
type Genre int32
この型には、与えられた値の名前を返すString()
メソッドがあります。
Enum()
メソッドは、新しく割り当てられたメモリを与えられた値で初期化し、対応するポインタを返します。
func (Genre) Enum() *Genre
protocol bufferコンパイラは、enumの各値に対して定数を生成します。メッセージ内のenumの場合、定数は囲んでいるメッセージの名前で始まります。
const (
Venue_KIND_UNSPECIFIED Venue_Kind = 0
Venue_KIND_CONCERT_HALL Venue_Kind = 1
Venue_KIND_STADIUM Venue_Kind = 2
Venue_KIND_BAR Venue_Kind = 3
Venue_KIND_OPEN_AIR_FESTIVAL Venue_Kind = 4
)
パッケージレベルのenumの場合、定数は代わりにenum名で始まります。
const (
Genre_GENRE_UNSPECIFIED Genre = 0
Genre_GENRE_ROCK Genre = 1
Genre_GENRE_INDIE Genre = 2
Genre_GENRE_DRUM_AND_BASS Genre = 3
)
protobufコンパイラはまた、整数値から文字列名へのマップと、名前から値へのマップを生成します。
var Genre_name = map[int32]string{
0: "GENRE_UNSPECIFIED",
1: "GENRE_ROCK",
2: "GENRE_INDIE",
3: "GENRE_DRUM_AND_BASS",
}
var Genre_value = map[string]int32{
"GENRE_UNSPECIFIED": 0,
"GENRE_ROCK": 1,
"GENRE_INDIE": 2,
"GENRE_DRUM_AND_BASS": 3,
}
.proto
言語では、複数のenumシンボルが同じ数値を持つことが許可されていることに注意してください。同じ数値を持つシンボルは同義語です。これらはGoでまったく同じ方法で表現され、複数の名前が同じ数値に対応します。逆マッピングには、数値から.protoファイルで最初に現れる名前への単一のエントリが含まれます。
エクステンション
エクステンション定義が与えられた場合:
extend Concert {
int32 promo_id = 123;
}
protocol bufferコンパイラは、E_Promo_id
という名前のprotoreflect.ExtensionType
値を生成します。この値は、proto.GetExtension
、proto.SetExtension
、proto.HasExtension
、およびproto.ClearExtension
関数と組み合わせて、メッセージ内のエクステンションにアクセスするために使用できます。GetExtension
関数とSetExtension
関数は、それぞれエクステンション値の型を含むinterface{}
値を返したり受け入れたりします。
単数スカラーエクステンションフィールドの場合、エクステンション値の型はスカラー値型テーブルの対応するGoの型です。
単数埋め込みメッセージエクステンションフィールドの場合、エクステンション値の型は*M
です。ここでM
はフィールドメッセージの型です。
repeatedエクステンションフィールドの場合、エクステンション値の型は単数型のスライスです。
たとえば、次の定義が与えられた場合:
extend Concert {
int32 singular_int32 = 1;
repeated bytes repeated_strings = 2;
Band singular_message = 3;
}
エクステンション値は次のようにアクセスできます。
m := &somepb.Concert{}
proto.SetExtension(m, extpb.E_SingularInt32, int32(1))
proto.SetExtension(m, extpb.E_RepeatedString, []string{"a", "b", "c"})
proto.SetExtension(m, extpb.E_SingularMessage, &extpb.Band{})
v1 := proto.GetExtension(m, extpb.E_SingularInt32).(int32)
v2 := proto.GetExtension(m, extpb.E_RepeatedString).([][]byte)
v3 := proto.GetExtension(m, extpb.E_SingularMessage).(*extpb.Band)
エクステンションは、別の型の中にネストして宣言することができます。たとえば、一般的なパターンは次のようなものです。
message Promo {
extend Concert {
int32 promo_id = 124;
}
}
この場合、ExtensionType
値はE_Promo_Concert
という名前になります。
サービス
Goコードジェネレータは、デフォルトではサービスの出力を生成しません。gRPCプラグインを有効にすると(gRPC Goクイックスタートガイドを参照)、gRPCをサポートするコードが生成されます。