Go生成コードガイド(Open)
proto2とproto3で生成されるコード間の相違点が強調表示されています - これらの相違点は、このドキュメントで説明されている生成コードにあり、両バージョンで同じであるベースAPIにはないことに注意してください。このドキュメントを読む前に、proto2言語ガイドおよび/またはproto3言語ガイドを読む必要があります。
注
古い生成コードAPI(Open Struct API)のドキュメントをご覧になっています。新しいOpaque APIの対応するドキュメントについては、Go生成コード(Opaque)を参照してください。Opaque APIの概要については、Go Protobuf: The new Opaque APIを参照してください。コンパイラの起動
プロトコルバッファコンパイラは、Goコードを生成するためにプラグインが必要です。Go 1.16以降を使用してインストールするには、以下を実行します。
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
これにより、protoc-gen-go
バイナリが$GOBIN
にインストールされます。インストール場所を変更するには、$GOBIN
環境変数を設定します。プロトコルバッファコンパイラがそれを見つけるには、$PATH
に含める必要があります。
プロトコルバッファコンパイラは、go_out
フラグを指定して起動されると、Go出力を生成します。go_out
フラグの引数は、コンパイラにGo出力を書き込ませたいディレクトリです。コンパイラは、.proto
ファイル入力ごとに単一のソースファイルを作成します。出力ファイルの名前は、.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
であり、example.com/project
がmodule
プレフィックスとして指定されている入力ファイル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
ファイル内で宣言することをお勧めします。これにより、.proto
ファイルのGoパッケージを.proto
ファイル自体と集中的に識別でき、protoc
を起動するときに渡されるフラグのセットを簡素化できます。特定の.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 |
エディション2023 | Open Struct API |
エディション2024+ | Opaque API |
.proto
ファイルでapi_level
エディション機能を設定することにより、APIを選択できます。これは、ファイルごとまたはメッセージごとに設定できます
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レベルを選択する場合は、最初にそのファイルをエディションに移行する必要があります。
メッセージ
簡単なメッセージ宣言が与えられた場合
message Artist {}
プロトコルバッファコンパイラは、Artist
と呼ばれる構造体を生成します。*Artist
は、proto.Message
インターフェースを実装します。
proto
パッケージは、バイナリ形式との間の変換など、メッセージを操作する関数を提供します。
proto.Message
インターフェースは、ProtoReflect
メソッドを定義します。このメソッドは、メッセージのリフレクションベースのビューを提供するprotoreflect.Message
を返します。
optimize_for
オプションは、Goコードジェネレーターの出力に影響を与えません。
複数のゴルーチンが同じメッセージに同時にアクセスする場合、次のルールが適用されます
- フィールドへの同時アクセス(読み取り)は安全ですが、1つの例外があります
- 遅延フィールドに初めてアクセスすることは変更です。
- 同じメッセージ内の異なるフィールドを変更することは安全です。
- フィールドを同時に変更することは安全ではありません。
proto
パッケージの関数(proto.Marshal
やproto.Size
など)と同時にメッセージを何らかの方法で変更することは安全ではありません。
ネストされた型
メッセージは別のメッセージ内で宣言できます。例
message Artist {
message Name {
}
}
この場合、コンパイラは2つの構造体(Artist
とArtist_Name
)を生成します。
フィールド
プロトコルバッファコンパイラは、メッセージ内で定義された各フィールドに対して構造体フィールドを生成します。このフィールドの正確な性質は、その型と、それが単数、繰り返し、マップ、またはoneofフィールドであるかどうかによって異なります。
生成されたGoフィールド名は常にキャメルケース命名を使用することに注意してください。.proto
ファイル内のフィールド名がアンダースコア付きの小文字を使用している場合でも(そうすべきであるように)。ケース変換は次のようになります
- 最初の文字はエクスポートのために大文字にされます。最初の文字がアンダースコアの場合、削除され、大文字のXが先頭に追加されます。
- 内部のアンダースコアの後に小文字が続く場合、アンダースコアは削除され、後続の文字は大文字にされます。
したがって、protoフィールドbirth_year
はGoではBirthYear
になり、_birth_year_2
はXBirthYear_2
になります。
単数スカラーフィールド(proto2)
これらのフィールド定義のいずれかについて
optional int32 birth_year = 1;
required int32 birth_year = 1;
コンパイラは、BirthYear
という名前の*int32
フィールドと、Artist
のint32
値、またはフィールドが設定されていない場合はデフォルト値を返すアクセスメソッドGetBirthYear()
を持つ構造体を生成します。デフォルトが明示的に設定されていない場合、代わりにその型のゼロ値が使用されます(数値の場合は0
、文字列の場合は空の文字列)。
他のスカラーフィールド型(bool
、bytes
、string
を含む)の場合、スカラー値型テーブルに従って、*int32
は対応するGo型に置き換えられます。
単数スカラーフィールド(proto3)
このフィールド定義について
int32 birth_year = 1;
optional int32 first_active_year = 2;
コンパイラは、BirthYear
という名前のint32
フィールドと、birth_year
のint32
値、またはフィールドが設定されていない場合はその型のゼロ値を返すアクセスメソッドGetBirthYear()
を持つ構造体を生成します(数値の場合は0
、文字列の場合は空の文字列)。
FirstActiveYear
構造体フィールドは、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;
}
コンパイラはGo構造体を生成します
type Concert struct {
Headliner *Band
}
メッセージフィールドはnil
に設定できます。これは、フィールドが設定解除され、事実上フィールドがクリアされることを意味します。これは、値をメッセージ構造体の「空の」インスタンスに設定することとは同等ではありません。
コンパイラは、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())
繰り返しフィールド
各繰り返しフィールドは、Goの構造体でT
フィールドのスライスを生成します。ここで、T
はフィールドの要素型です。繰り返しフィールドを持つこのメッセージの場合
message Concert {
// Best practice: use pluralized names for repeated fields:
// /programming-guides/style#repeated-fields
repeated Band support_acts = 1;
}
コンパイラはGo構造体を生成します
type Concert struct {
SupportActs []*Band
}
同様に、フィールド定義repeated bytes band_promo_images = 1;
の場合、コンパイラはBandPromoImage
という名前の[][]byte
フィールドを持つGo構造体を生成します。repeated MusicGenre genres = 2;
のような繰り返しの列挙型の場合、コンパイラはGenre
と呼ばれる[]MusicGenre
フィールドを持つ構造体を生成します。
次の例は、フィールドを設定する方法を示しています
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[TKey]TValue
型の構造体のフィールドを生成します。ここで、TKey
はフィールドのキー型であり、TValue
はフィールドの値型です。マップフィールドを持つこのメッセージの場合
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構造体を生成します
type MerchBooth struct {
Items map[string]*MerchItem
}
Oneofフィールド
oneofフィールドの場合、protobufコンパイラは、インターフェース型isMessageName_MyField
を持つ単一のフィールドを生成します。また、oneof内の単数フィールドごとに構造体を生成します。これらはすべて、このisMessageName_MyField
インターフェースを実装します。
oneofフィールドを持つこのメッセージの場合
package account;
message Profile {
oneof avatar {
string image_url = 1;
bytes image_data = 2;
}
}
コンパイラは構造体を生成します
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;
// ...
}
プロトコルバッファコンパイラは、型と、その型を持つ一連の定数を生成します
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
プロトコルバッファコンパイラは、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ファイルで最初に表示される名前への数値の単一のエントリが含まれています。
拡張機能(proto2)
拡張機能の定義が与えられた場合
extend Concert {
optional int32 promo_id = 123;
}
プロトコルバッファコンパイラは、E_Promo_id
という名前のprotoreflect.ExtensionType
値を生成します。この値は、メッセージ内の拡張機能にアクセスするために、proto.GetExtension
、proto.SetExtension
、proto.HasExtension
、およびproto.ClearExtension
関数で使用できます。GetExtension
関数とSetExtension
関数はそれぞれ、拡張機能の型を含むinterface{}
値を返し、受け入れます。
単数スカラー拡張機能フィールドの場合、拡張機能の値の型は、スカラー値型テーブルからの対応するGo型です。
単数埋め込みメッセージ拡張機能フィールドの場合、拡張機能の値の型は*M
です。ここで、M
はフィールドメッセージ型です。
繰り返し拡張機能フィールドの場合、拡張機能の値の型は単数型のスライスです。
たとえば、次の定義が与えられた場合
extend Concert {
optional int32 singular_int32 = 1;
repeated bytes repeated_strings = 2;
optional 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 {
optional int32 promo_id = 124;
}
}
この場合、ExtensionType
値はE_Promo_Concert
という名前になります。
サービス
Goコードジェネレーターは、デフォルトではサービスに対して出力を生成しません。 gRPCプラグインを有効にする場合(gRPC Goクイックスタートガイドを参照)、gRPCをサポートするためのコードが生成されます。