Go FAQ

Go でのプロトコルバッファの実装に関するよくある質問とその回答のリストです。

バージョン

github.com/golang/protobufgoogle.golang.org/protobuf の違いは何ですか?

github.com/golang/protobuf モジュールは、オリジナルの Go プロトコルバッファ API です。

google.golang.org/protobuf モジュールは、シンプルさ、使いやすさ、安全性を追求して設計された、この API の更新バージョンです。更新された API の主要な機能は、リフレクションのサポートと、ユーザー向けの API と基盤となる実装の分離です。

新しいコードでは google.golang.org/protobuf を使用することをお勧めします。

github.com/golang/protobuf のバージョン v1.4.0 以降は、新しい実装をラップし、プログラムが新しい API を段階的に採用することを可能にします。例えば、github.com/golang/protobuf/ptypes で定義されているよく知られた型は、新しいモジュールで定義されている型のエイリアスにすぎません。したがって、google.golang.org/protobuf/types/known/emptypbgithub.com/golang/protobuf/ptypes/empty は相互に交換可能です。

proto1proto2proto3、およびエディションとは何ですか?

これらはプロトコルバッファの言語の改訂版です。protobuf の Go 実装とは異なります。

  • エディションは、プロトコルバッファを作成するための最新かつ推奨される方法です。新しい機能は新しいエディションの一部としてリリースされます。詳細については、「プロトコルバッファ エディション」を参照してください。

  • proto3 は言語のレガシーバージョンです。新しいコードではエディションを使用することをお勧めします。

  • proto2 は言語のレガシーバージョンです。proto3 およびエディションに取って代わられていますが、proto2 は引き続き完全にサポートされています。

  • proto1 は言語の廃止されたバージョンです。オープンソースとしてリリースされたことはありません。

いくつかの異なる Message タイプがあります。どれを使用すべきですか?

  • "google.golang.org/protobuf/proto".Message は、現在のバージョンのプロトコルバッファコンパイラによって生成されたすべてのメッセージによって実装されるインターフェース型です。proto.Marshalproto.Clone のように、任意のメッセージを操作する関数は、この型を受け入れたり返したりします。

  • "google.golang.org/protobuf/reflect/protoreflect".Message は、メッセージのリフレクションビューを記述するインターフェース型です。

    proto.MessageProtoReflect メソッドを呼び出すと、protoreflect.Message を取得できます。

  • "google.golang.org/protobuf/reflect/protoreflect".ProtoMessage"google.golang.org/protobuf/proto".Message のエイリアスです。これら2つの型は交換可能です。

  • "github.com/golang/protobuf/proto".Message は、レガシー Go プロトコルバッファ API によって定義されたインターフェース型です。生成されたすべてのメッセージ型はこのインターフェースを実装しますが、このインターフェースはこれらのメッセージに期待される動作を記述していません。新しいコードはこの型の使用を避けるべきです。

一般的な問題

go install」:working directory is not part of a module

Go 1.15 以前では、環境変数 GO111MODULE=on を設定しており、モジュールディレクトリ外で go install コマンドを実行しています。GO111MODULE=auto を設定するか、環境変数を設定解除してください。

Go 1.16 以降では、明示的なバージョンを指定することでモジュールの外で go install を呼び出すことができます: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

定数 -1 が protoimpl.EnforceVersion をオーバーフローしています

より新しいバージョンの "google.golang.org/protobuf" モジュールを必要とする、生成された .pb.go ファイルを使用しています。

より新しいバージョンに更新するには

go get -u google.golang.org/protobuf/proto

未定義: "github.com/golang/protobuf/proto".ProtoPackageIsVersion4

より新しいバージョンの "github.com/golang/protobuf" モジュールを必要とする、生成された .pb.go ファイルを使用しています。

より新しいバージョンに更新するには

go get -u github.com/golang/protobuf/proto

プロトコルバッファの名前空間の競合とは何ですか?

Go バイナリにリンクされたすべてのプロトコルバッファ宣言は、グローバルレジストリに挿入されます。

すべての protobuf 宣言(例:enum、enum 値、メッセージ)には絶対名があり、これはパッケージ名.proto ソースファイル内の宣言の相対名(例:my.proto.package.MyMessage.NestedMessage)を連結したものです。protobuf 言語は、すべての宣言が普遍的に一意であると仮定しています。

Go バイナリにリンクされた2つの protobuf 宣言が同じ名前を持っている場合、これは名前空間の競合を引き起こし、レジストリがその宣言を名前で適切に解決することが不可能になります。使用されている Go protobuf のバージョンによっては、これは初期化時にパニックを引き起こすか、競合をサイレントに削除し、後に実行時に潜在的なバグにつながる可能性があります。

プロトコルバッファの名前空間の競合を修正するにはどうすればよいですか?

名前空間の競合を修正する最適な方法は、競合が発生している理由によって異なります。

名前空間の競合が起こる一般的な方法

  • ベンダーの .proto ファイル。1つの .proto ファイルが2つ以上の Go パッケージに生成され、同じ Go バイナリにリンクされると、生成された Go パッケージ内のすべての protobuf 宣言で競合が発生します。これは通常、.proto ファイルがベンダー化され、そこから Go パッケージが生成された場合、または生成された Go パッケージ自体がベンダー化された場合に発生します。ユーザーはベンダー化を避け、代わりにその .proto ファイルの中央の Go パッケージに依存すべきです。

    • .proto ファイルが外部の当事者によって所有されており、go_package オプションが欠落している場合、その .proto ファイルの所有者と調整して、多数のユーザーがすべて依存できる中央の Go パッケージを指定する必要があります。
  • 欠落しているか、または一般的な proto パッケージ名。 .proto ファイルがパッケージ名を指定していない場合、または過度に一般的なパッケージ名(例:「my_service」)を使用している場合、そのファイル内の宣言が宇宙の他の場所の他の宣言と競合する可能性が高くなります。すべての .proto ファイルが普遍的に一意になるように意図的に選択されたパッケージ名(例:会社名のプレフィックスを付ける)を持つことをお勧めします。

google.golang.org/protobuf モジュールの v1.26.0 以降では、複数の競合する protobuf 名がリンクされている Go プログラムが起動すると、ハードエラーが報告されます。競合の原因を修正することが望ましいですが、致命的なエラーはすぐに次の2つの方法のいずれかで回避できます。

  1. コンパイル時。競合処理のデフォルトの動作は、リンカーで初期化される変数でコンパイル時に指定できます: go build -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn"

  2. プログラム実行時。特定の Go バイナリを実行する際の競合処理の動作は、環境変数で設定できます: GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn ./main

プロトコルバッファのエディションを使用するにはどうすればよいですか?

protobuf エディションを使用するには、.proto ファイルでエディションを指定する必要があります。たとえば、2023 年版を使用するには、.proto ファイルの先頭に以下を追加します。

edition = "2023";

次に、プロトコルバッファコンパイラは、指定されたエディションと互換性のある Go コードを生成します。エディションを使用すると、.proto ファイルの特定の機能を有効または無効にすることもできます。詳細については、「プロトコルバッファのエディション」を参照してください。

生成された Go コードの動作を制御するにはどうすればよいですか?

エディションを使用すると、.proto ファイルで特定の機能を有効または無効にすることで、生成された Go コードの動作を制御できます。たとえば、実装の API 動作を設定するには、.proto ファイルに以下を追加します。

edition = "2023";

option features.(pb.go).api_level = API_OPAQUE;

api_levelAPI_OPAQUE に設定されている場合、プロトコルバッファコンパイラによって生成された Go コードは構造体フィールドを非表示にし、直接アクセスできなくなります。代わりに、フィールドの取得、設定、またはクリアのための新しいアクセッサメソッドが作成されます。

利用可能な機能とその説明の完全なリストについては、「エディションの機能」を参照してください。

reflect.DeepEqual が protobuf メッセージで予期しない動作をするのはなぜですか?

生成されたプロトコルバッファメッセージ型には、同等のメッセージ間でも変化する可能性のある内部状態が含まれています。

さらに、reflect.DeepEqual 関数はプロトコルバッファメッセージのセマンティクスを認識しておらず、違いがないのに違いを報告することがあります。たとえば、nil マップを含むマップフィールドと、ゼロ長で非nil マップを含むマップフィールドはセマンティック的には同等ですが、reflect.DeepEqual によっては等しくないと報告されます。

メッセージ値を比較するには、proto.Equal 関数を使用してください。

テストでは、"github.com/google/go-cmp/cmp" パッケージを protocmp.Transform() オプションと共に使用することもできます。cmp パッケージは任意のデータ構造を比較でき、cmp.Diff は値間の違いを人間が読める形式で報告します。

if diff := cmp.Diff(a, b, protocmp.Transform()); diff != "" {
  t.Errorf("unexpected difference:\n%v", diff)
}

ハイラムの法則

ハイラムの法則とは何ですか、そしてなぜこの FAQ に含まれているのですか?

ハイラムの法則は次のように述べています。

API の利用者が十分な数に達すると、契約で何を約束しても関係ない。システムの観察可能なすべての動作は、誰かに依存されるだろう。

最新バージョンの Go プロトコルバッファ API の設計目標は、可能な限り、将来安定性を維持できない可能性のある観察可能な動作を提供しないことです。私達の哲学では、約束しない領域での意図的な不安定性は、誤った仮定にプロジェクトが長く依存した後、将来変更される可能性のある安定性の幻想を与えるよりも優れていると考えています。

エラーのテキストが変わり続けるのはなぜですか?

エラーの正確なテキストに依存するテストは脆弱であり、そのテキストが変更されると頻繁に壊れます。テストでのエラーテキストの安全でない使用を阻止するため、このモジュールによって生成されるエラーのテキストは意図的に不安定です。

エラーが protobuf モジュールによって生成されたかどうかを識別する必要がある場合、すべてエラーが errors.Is に従って proto.Error に一致することを保証します。

protojson の出力が変わり続けるのはなぜですか?

Go の プロトコルバッファの JSON 形式の実装の長期的な安定性については保証しません。仕様は有効な JSON が何かを規定しているだけで、マーシャラーが特定のメッセージを正確にフォーマットする方法についての規範的な形式は規定していません。出力が安定しているという幻想を避けるため、バイトごとの比較が失敗する可能性のあるわずかな違いを意図的に導入しています。

ある程度の出力安定性を得るために、出力を JSON フォーマッターに通すことをお勧めします。

prototext の出力が変わり続けるのはなぜですか?

Go のテキスト形式の実装の長期的な安定性については保証しません。protobuf テキスト形式の規範的な仕様はなく、将来 prototext パッケージの出力に改善を加える能力を維持したいと考えています。パッケージの出力の安定性を約束しないため、ユーザーが出力に依存するのを阻止するために、意図的に不安定性を導入しています。

ある程度の安定性を得るには、prototext の出力を txtpbfmt プログラムに通すことをお勧めします。フォーマッターは、Go で parser.Format を使用して直接呼び出すことができます。

その他

プロトコルバッファメッセージをハッシュキーとして使用するにはどうすればよいですか?

マーシャリングされたプロトコルバッファメッセージの出力が時間の経過とともに安定していることが保証される、規範的なシリアル化が必要です。残念ながら、現時点では規範的なシリアル化の仕様は存在しません。自分で書くか、必要としない方法を見つける必要があります。

Go プロトコルバッファ実装に新機能を追加できますか?

もしかしたら。提案は常に歓迎していますが、新しいものを追加することには非常に慎重です。

Go のプロトコルバッファ実装は、他の言語実装と一貫していることを目指しています。そのため、Go に特化した機能は避ける傾向があります。Go 固有の機能は、プロトコルバッファが言語に依存しないデータ交換形式であるという目標を妨げます。

あなたのアイデアが Go の実装に特有のものでない限り、protobuf ディスカッショングループに参加して、そこで提案してください。

Go の実装に関するアイデアがある場合は、問題トラッカーに問題を提出してください: https://github.com/golang/protobuf/issues

Marshal または Unmarshal にオプションを追加してカスタマイズできますか?

そのオプションが他の実装(例:C++、Java)に存在する場合のみです。プロトコルバッファのエンコーディング(バイナリ、JSON、テキスト)は実装間で一貫していなければならないため、ある言語で書かれたプログラムは別の言語で書かれたメッセージを読み取ることができます。

Marshal 関数によって出力されるデータや Unmarshal 関数によって読み取られるデータに影響を与える Go 実装へのオプションは、少なくとも他のサポートされている実装のいずれかに同等のオプションが存在しない限り、追加しません。

protoc-gen-go によって生成されるコードをカスタマイズできますか?

一般的に、いいえ。プロトコルバッファは言語に依存しないデータ交換形式であることを意図しており、実装固有のカスタマイズはその意図に反します。