Go 生成コードガイド (Opaque)
proto2 と proto3 の生成コード間の違いは強調されています。これらの違いは、このドキュメントで説明されている生成コードにあり、両バージョンで同じ基本APIではありません。このドキュメントを読む前に、proto2 言語ガイドおよび/またはproto3 言語ガイドを読むことをお勧めします。
注
現在ご覧になっているのは、現在のバージョンである Opaque API のドキュメントです。古い Open Struct API を使用する .proto ファイル(各 .proto ファイルの API レベル設定で確認できます)を扱っている場合は、対応するドキュメントとしてGo 生成コード (Open) を参照してください。Opaque API の導入については、Go Protobuf: 新しい 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
で、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";
コンパイラを呼び出す際に、1つ以上の M${PROTO_FILE}=${GO_IMPORT_PATH}
フラグを渡すことで、Go インポートパスをコマンドラインで指定できます。使用例:
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: 新しい Opaque API のブログ投稿を参照してください。
あなたの .proto
ファイルが使用する構文に応じて、使用されるAPIは次のとおりです。
.proto 構文 | API レベル |
---|---|
proto2 | Open Struct API |
proto3 | Open Struct API |
エディション 2023 | Open Struct API |
エディション 2024+ | Opaque API |
API は、.proto
ファイルで api_level
エディション機能を設定することで選択できます。これはファイルごと、またはメッセージごとに設定できます。
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 {
}
}
この場合、コンパイラは Artist
と Artist_Name
の2つの構造体を生成します。
フィールド
プロトコルバッファコンパイラは、メッセージ内で定義された各フィールドに対して、アクセサメソッド(セッターとゲッター)を生成します。
生成される Go アクセサメソッドは、.proto
ファイル内のフィールド名がアンダースコアを含む小文字(あるべき姿)であっても、常にキャメルケースの命名を使用することに注意してください。ケース変換は以下のようになります。
- 最初の文字はエクスポートのために大文字になります。最初の文字がアンダースコアの場合、それは削除され、大文字の X が前置されます。
- 内部のアンダースコアの後に小文字が続く場合、アンダースコアは削除され、続く文字が大文字になります。
したがって、Go では GetBirthYear()
メソッドを使用して proto フィールド birth_year
にアクセスでき、GetXBirthYear_2()
を使用して _birth_year_2
にアクセスできます。
単一スカラーフィールド (proto2)
いずれかのフィールド定義に対して:
optional int32 birth_year = 1;
required int32 birth_year = 1;
コンパイラは以下のアクセサメソッドを生成します。
func (m *Artist) GetBirthYear() int32 { ... }
func (m *Artist) SetBirthYear(v int32) { ... }
func (m *Artist) HasBirthYear() bool { ... }
func (m *Artist) ClearBirthYear() { ... }
アクセサメソッド GetBirthYear()
は、birth_year
内の int32
値、またはフィールドが設定されていない場合はデフォルト値を返します。デフォルトが明示的に設定されていない場合、その型のゼロ値が代わりに使用されます(数値の場合は0
、文字列の場合は空文字列)。
その他のスカラーフィールド型(bool
、bytes
、string
を含む)の場合、スカラー値型テーブルに従って int32
は対応する Go 型に置き換えられます。
単一スカラーフィールド (proto3)
このフィールド定義の場合:
int32 birth_year = 1;
optional int32 first_active_year = 2;
コンパイラは以下のアクセサメソッドを生成します。
func (m *Artist) GetBirthYear() int32 { ... }
func (m *Artist) SetBirthYear(v int32) { ... }
// NOTE: No HasBirthYear() or ClearBirthYear() methods;
// proto3 fields only have presence when declared as optional:
// /programming-guides/field_presence.md
func (m *Artist) GetFirstActiveYear() int32 { ... }
func (m *Artist) SetFirstActiveYear(v int32) { ... }
func (m *Artist) HasFirstActiveYear() bool { ... }
func (m *Artist) ClearFirstActiveYear() { ... }
アクセサメソッド GetBirthYear()
は、birth_year
内の int32
値、またはフィールドが設定されていない場合はその型のゼロ値を返します(数値の場合は0
、文字列の場合は空文字列)。
その他のスカラーフィールド型(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 { ... }
func (m *Concert) GetHeadliner() *Band { ... }
func (m *Concert) SetHeadliner(v *Band) { ... }
func (m *Concert) HasHeadliner() bool { ... }
func (m *Concert) ClearHeadliner() { ... }
GetHeadliner()
アクセサメソッドは、m
が nil であっても安全に呼び出すことができます。これにより、中間で nil
チェックをすることなく、get 呼び出しを連鎖させることができます。
var m *Concert // defaults to nil
log.Infof("GetFoundingYear() = %d (no panic!)", m.GetHeadliner().GetFoundingYear())
フィールドが設定されていない場合、ゲッターはそのフィールドのデフォルト値を返します。メッセージの場合、デフォルト値は nil ポインタです。
ゲッターとは異なり、セッターは nil チェックを自動的に行いません。したがって、nil の可能性があるメッセージに対してセッターを安全に呼び出すことはできません。
繰り返しフィールド
繰り返しフィールドの場合、アクセサメソッドはスライス型を使用します。繰り返しフィールドを持つこのメッセージの場合:
message Concert {
// Best practice: use pluralized names for repeated fields:
// /programming-guides/style#repeated-fields
repeated Band support_acts = 1;
}
コンパイラは以下のアクセサメソッドを持つ Go 構造体を生成します。
type Concert struct { ... }
func (m *Concert) GetSupportActs() []*Band { ... }
func (m *Concert) SetSupportActs(v []*Band) { ... }
同様に、フィールド定義 repeated bytes band_promo_images = 1;
の場合、コンパイラは [][]byte
型を扱うアクセサを生成します。繰り返し列挙型 repeated MusicGenre genres = 2;
の場合、コンパイラは []MusicGenre
型を扱うアクセサを生成します。
以下の例は、ビルダーを使用して Concert
メッセージを構築する方法を示しています。
concert := Concert_builder{
SupportActs: []*Band{
{}, // First element.
{}, // Second element.
},
}.Build()
あるいは、セッターを使用することもできます。
concert := &Concert{}
concert.SetSupportActs([]*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.
マップフィールド
各マップフィールドは、TKey
がフィールドのキー型で TValue
がフィールドの値型である map[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 { ... }
func (m *MerchBooth) GetItems() map[string]*MerchItem { ... }
func (m *MerchBooth) SetItems(v map[string]*MerchItem) { ... }
Oneof フィールド
oneof フィールドの場合、protobuf コンパイラは oneof 内の各単一フィールドに対してアクセサを生成します。
oneof フィールドを持つこのメッセージの場合:
package account;
message Profile {
oneof avatar {
string image_url = 1;
bytes image_data = 2;
}
}
コンパイラは以下のアクセサメソッドを持つ Go 構造体を生成します。
type Profile struct { ... }
func (m *Profile) WhichAvatar() case_Profile_Avatar { ... }
func (m *Profile) GetImageUrl() string { ... }
func (m *Profile) GetImageData() []byte { ... }
func (m *Profile) SetImageUrl(v string) { ... }
func (m *Profile) SetImageData(v []byte) { ... }
func (m *Profile) HasAvatar() bool { ... }
func (m *Profile) HasImageUrl() bool { ... }
func (m *Profile) HasImageData() bool { ... }
func (m *Profile) ClearAvatar() { ... }
func (m *Profile) ClearImageUrl() { ... }
func (m *Profile) ClearImageData() { ... }
以下の例は、ビルダーを使用してフィールドを設定する方法を示しています。
p1 := accountpb.Profile_builder{
ImageUrl: proto.String("https://example.com/image.png"),
}.Build()
…または、同等の方法としてセッターを使用する方法:
// imageData is []byte
imageData := getImageData()
p2 := &accountpb.Profile{}
p2.SetImageData(imageData)
フィールドにアクセスするには、WhichAvatar()
の結果に対して switch ステートメントを使用できます。
switch m.WhichAvatar() {
case accountpb.Profile_ImageUrl_case:
// Load profile image based on URL
// using m.GetImageUrl()
case accountpb.Profile_ImageData_case:
// Load profile image based on bytes
// using m.GetImageData()
case accountpb.Profile_Avatar_not_set_case:
// The field is not set.
default:
return fmt.Errorf("Profile.Avatar has an unexpected new oneof field %v", x)
}
ビルダー
ビルダーは、特に単体テストのようなネストされたメッセージを扱う場合に、単一の式内でメッセージを構築し初期化するための便利な方法です。
他の言語(Javaなど)のビルダーとは異なり、Go の protobuf ビルダーは関数間で受け渡しされることを意図していません。代わりに、Build()
をすぐに呼び出して、結果のプロトメッセージを渡し、セッターを使用してフィールドを変更してください。
列挙型
以下のような列挙型がある場合:
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
)
メッセージ内の列挙型(上記のようなもの)の場合、型名はメッセージ名から始まります。
type Venue_Kind int32
パッケージレベルの列挙型の場合:
enum Genre {
GENRE_UNSPECIFIED = 0;
GENRE_ROCK = 1;
GENRE_INDIE = 2;
GENRE_DRUM_AND_BASS = 3;
// ...
}
Go の型名は proto の列挙型名から変更されません。
type Genre int32
この型には、指定された値の名前を返す String()
メソッドがあります。
Enum()
メソッドは、新しく割り当てられたメモリを指定された値で初期化し、対応するポインタを返します。
func (Genre) Enum() *Genre
プロトコルバッファコンパイラは、列挙型の各値に対して定数を生成します。メッセージ内の列挙型の場合、定数はその列挙型を囲むメッセージの名前から始まります。
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
)
パッケージレベルの列挙型の場合、定数は代わりに列挙型名から始まります。
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
言語では、複数の列挙シンボルが同じ数値を持つことを許可していることに注意してください。同じ数値を持つシンボルは同義語です。これらは 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 をサポートするためのコードが生成されます。