Go 生成コードガイド(オープン)

Protocol Buffers コンパイラが、特定のプロトコル定義に対してどのような Go コードを生成するのかを正確に説明します。

proto2 と proto3 で生成されるコードの違いが強調されています。これらの違いは、このドキュメントで説明されている生成コードにあり、両バージョンで同じ基本APIにはありません。このドキュメントを読む前に、proto2 言語ガイド または proto3 言語ガイド を読む必要があります。

コンパイラの呼び出し

Protocol Buffers コンパイラが Go コードを生成するには、プラグインが必要です。Go 1.16 以降を使用して、以下のコマンドを実行してインストールします。

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

これにより、`protoc-gen-go` バイナリが `$GOBIN` にインストールされます。インストール場所を変更するには、`$GOBIN` 環境変数を設定してください。Protocol Buffers コンパイラがそれを見つけるためには、`$PATH` に含まれている必要があります。

Protocol Buffers コンパイラは、`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";

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 レベル
proto2Open Struct API
proto3Open Struct API
エディション 2023Open 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 {}

Protocol Buffers コンパイラは、`Artist` と呼ばれる構造体を生成します。`*Artist` は proto.Message インターフェースを実装します。

proto パッケージは、バイナリ形式への変換やバイナリ形式からの変換を含む、メッセージを操作する関数を提供します。

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

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

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

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

ネストされた型

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

message Artist {
  message Name {
  }
}

この場合、コンパイラは `Artist` と `Artist_Name` の2つの構造体を生成します。

フィールド

Protocol Buffers コンパイラは、メッセージ内に定義された各フィールドに対して構造体フィールドを生成します。このフィールドの正確な性質は、その型と、それが単一、繰り返し、マップ、または oneof フィールドであるかどうかによって異なります。

生成される Go フィールド名は、たとえ `.proto` ファイルのフィールド名がアンダースコアを含む小文字(本来あるべき姿)であっても、常にキャメルケースの命名を使用することに注意してください。大文字小文字の変換は次のように機能します。

  1. 最初の文字はエクスポートのために大文字になります。最初の文字がアンダースコアの場合、それが削除され、大文字の X が前に付けられます。
  2. 内部のアンダースコアの後に小文字が続く場合、アンダースコアは削除され、続く文字が大文字になります。

したがって、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` 値、またはフィールドが設定されていない場合はその型のゼロ値(数値の場合は `0`、文字列の場合は空文字列)を返すアクセサメソッド `GetBirthYear()` を持つ構造体を生成します。

`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` チェックを行わずにゲッター呼び出しを連鎖させることが可能になります。

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.

マップフィールド

各マップフィールドは、Go の構造体内に `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)
}

コンパイラはまた、ゲッターメソッド `func (m *Profile) GetImageUrl() string` と `func (m *Profile) GetImageData() []byte` を生成します。各ゲッター関数は、そのフィールドの値、または設定されていない場合はゼロ値を返します。

列挙型

以下のような列挙型の場合:

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 Buffers コンパイラは、型と、その型を持つ一連の定数を生成します。

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

Protocol Buffers コンパイラは、列挙型内の各値に対して定数を生成します。メッセージ内の列挙型の場合、定数は囲むメッセージの名前で始まります。

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;
}

Protocol Buffers コンパイラは、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 Quickstart guide を参照)、gRPC をサポートするためのコードが生成されます。