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 は互換的に使用できます。

proto1proto2、および proto3 とは何ですか?

これらは、プロトコルバッファ言語のリビジョンです。これは、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」: 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 ファイル。 単一の .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

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

Hyrum の法則

Hyrum の法則とは何ですか?また、なぜこの FAQ に記載されているのですか?

Hyrum の法則 は述べています:

API の十分な数のユーザーがいる場合、契約で約束することは問題ではありません。システムのすべての観察可能な動作は、誰かによって依存されます。

最新バージョンの Go プロトコルバッファ API の設計目標は、可能な限り、将来安定性を維持することを約束できない観察可能な動作を提供することを避けることです。約束をしない分野での意図的な不安定性は、安定性の錯覚を与えるよりも優れており、プロジェクトが潜在的にその誤った仮定に長期間依存した後で、将来的に変更される可能性があります。

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

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

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

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

プロトコルバッファの JSON 形式 の Go 実装の長期的な安定性については何も約束しません。仕様は、何が有効な JSON であるかのみを指定しますが、marshaller が特定のメッセージを正確にフォーマットする方法の正準形式の仕様は提供していません。出力が安定しているという錯覚を与えないように、バイト単位の比較が失敗する可能性が高くなるように、意図的にわずかな違いを導入しています。

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

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

テキスト形式の Go 実装の長期的な安定性については何も約束しません。protobuf テキスト形式の正準仕様はなく、将来 prototext パッケージの出力を改善する機能を保持したいと考えています。パッケージの出力の安定性を約束していないため、ユーザーがそれに依存することを思いとどまらせるために、意図的に不安定性を導入しました。

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

その他

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

プロトコルバッファメッセージの marshalled 出力が時間の経過とともに安定することが保証される正準シリアライズが必要です。残念ながら、現時点では正準シリアライズの仕様は存在しません。自分で作成するか、必要性を回避する方法を見つける必要があります。

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

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

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

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

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

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

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

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

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

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