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 で定義されている Well-Known Types は、新しいモジュールで定義されているものの単なるエイリアスです。したがって、google.golang.org/protobuf/types/known/emptypbgithub.com/golang/protobuf/ptypes/empty は相互に交換可能です。

proto1proto2proto3 とは何ですか?

これらはプロトコルバッファの*言語*のリビジョンです。これはprotobufのGo*実装*とは異なります。

  • proto3 は現在の言語バージョンです。これは最も一般的に使用されている言語バージョンです。新しいコードでは 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」: 作業ディレクトリはモジュールの一部ではありません

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 ファイルがパッケージ名を指定しないか、汎用的なパッケージ名(例:「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

なぜ 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 実装に追加することはありません。ただし、同等のオプションが少なくとも1つの他のサポートされている実装に存在する場合はこの限りではありません。

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

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