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
で、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
ファイルの Go パッケージを .proto
ファイル自体で一元的に識別できるようにし、protoc
を起動するときに渡されるフラグのセットを簡素化するために、.proto
ファイル内で宣言することをお勧めします。特定の .proto
ファイルの Go インポートパスが .proto
ファイル自体とコマンドラインの両方で提供されている場合、後者が前者よりも優先されます。
Go インポートパスは、go_package
オプションを Go パッケージの完全なインポートパスで宣言することによって、.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
ファイルをインポートするときに生成する必要がある import ステートメントを決定するために使用されます。たとえば、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 |
edition 2023 | Open Struct API |
edition 2024+ | Opaque API |
.proto
ファイルで api_level
editions 機能を設定することで 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
という名前の struct を生成します。*Artist
は proto.Message
インターフェースを実装します。
proto
パッケージ は、バイナリ形式との間で変換するなど、メッセージを操作する関数を提供します。
proto.Message
インターフェースは、ProtoReflect
メソッドを定義します。このメソッドは、メッセージのリフレクションベースのビューを提供する protoreflect.Message
を返します。
optimize_for
オプションは、Go コードジェネレーターの出力には影響しません。
複数のゴルーチンが同じメッセージに同時にアクセスする場合、次のルールが適用されます。
- フィールドへの同時アクセス (読み取り) は安全ですが、1 つ例外があります。
- 遅延フィールド に初めてアクセスすると、変更になります。
- 同じメッセージ内の異なるフィールドを変更することは安全です。
- フィールドを同時に変更することは安全ではありません。
proto
パッケージ の関数 (たとえば、proto.Marshal
やproto.Size
など) と同時にメッセージを何らかの方法で変更することは安全ではありません。
ネストされた型
メッセージは別のメッセージ内に宣言できます。例:
message Artist {
message Name {
}
}
この場合、コンパイラーは 2 つの struct、Artist
と Artist_Name
を生成します。
フィールド
プロトコルバッファコンパイラーは、メッセージ内で定義された各フィールドに対してアクセサーメソッド (セッターとゲッター) を生成します。
生成された 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 struct を生成します。
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 struct を生成します。
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.
マップフィールド
各マップフィールドは、型 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 struct を生成します。
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 struct を生成します。
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()
をすぐに呼び出し、結果の proto メッセージを代わりに渡し、セッターを使用してフィールドを変更します。
列挙型
次のような列挙型が与えられた場合:
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 をサポートするためのコードが生成されます。