Go で生成されたコードガイド (Opaque)

プロトコルバッファコンパイラーが、与えられたプロトコル定義に対してどのような Go コードを生成するかを正確に記述します。

proto2 と proto3 で生成されるコードの違いはすべて強調表示されています。これらの違いは、このドキュメントで説明されている生成されたコードにあり、両バージョンで同じであるベース API にはないことに注意してください。このドキュメントを読む前に、proto2 言語ガイド または proto3 言語ガイド を読むことをお勧めします。

コンパイラの起動

プロトコルバッファコンパイラーは、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/projectmodule プレフィックスとして指定されている入力ファイル 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.protobar/baz.proto を読み取り、出力ファイル foo.pb.gobar/baz.pb.goout ディレクトリに書き込みます。コンパイラーは、必要に応じてネストされた出力サブディレクトリを自動的に作成しますが、出力ディレクトリ自体は作成しません。

パッケージ

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.protob.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 レベル
proto2Open Struct API
proto3Open Struct API
edition 2023Open 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 を生成します。*Artistproto.Message インターフェースを実装します。

proto パッケージ は、バイナリ形式との間で変換するなど、メッセージを操作する関数を提供します。

proto.Message インターフェースは、ProtoReflect メソッドを定義します。このメソッドは、メッセージのリフレクションベースのビューを提供する protoreflect.Message を返します。

optimize_for オプションは、Go コードジェネレーターの出力には影響しません。

複数のゴルーチンが同じメッセージに同時にアクセスする場合、次のルールが適用されます。

  • フィールドへの同時アクセス (読み取り) は安全ですが、1 つ例外があります。
  • 同じメッセージ内の異なるフィールドを変更することは安全です。
  • フィールドを同時に変更することは安全ではありません。
  • proto パッケージ の関数 (たとえば、proto.Marshalproto.Size など) と同時にメッセージを何らかの方法で変更することは安全ではありません。

ネストされた型

メッセージは別のメッセージ内に宣言できます。例:

message Artist {
  message Name {
  }
}

この場合、コンパイラーは 2 つの struct、ArtistArtist_Name を生成します。

フィールド

プロトコルバッファコンパイラーは、メッセージ内で定義された各フィールドに対してアクセサーメソッド (セッターとゲッター) を生成します。

生成された Go アクセサーメソッドは、.proto ファイルのフィールド名がアンダースコア付きの小文字を使用している場合でも (推奨されているように)、常にキャメルケースの名前を使用することに注意してください。ケース変換は次のように機能します。

  1. 最初の手紙はエクスポートのために大文字にされます。最初の文字がアンダースコアの場合、削除され、大文字の X が先頭に追加されます。
  2. 内部のアンダースコアの後に小文字が続く場合、アンダースコアは削除され、後続の文字は大文字にされます。

したがって、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_yearint32 値、またはフィールドが設定されていない場合はデフォルト値を返します。デフォルトが明示的に設定されていない場合、代わりにその型の ゼロ値 (数値の場合は 0、文字列の場合は空文字列) が使用されます。

他のスカラーフィールド型 (boolbytesstring を含む) の場合、スカラー値型テーブル に従って、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_yearint32 値、またはフィールドが設定されていない場合はその型の ゼロ値 (数値の場合は 0、文字列の場合は空文字列) を返します。

他のスカラーフィールド型 (boolbytesstring を含む) の場合、スカラー値型テーブル に従って、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.GetExtensionproto.SetExtensionproto.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 をサポートするためのコードが生成されます。