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 マップを含むマップフィールドと、長さ0の非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 の最新バージョンの設計目標の1つは、将来安定性を維持することを約束できない観察可能な動作を、可能な限り提供しないことです。将来、プロジェクトがその誤った仮定に長く依存していた後に変化するだけの安定性の錯覚を与えるよりも、約束しない領域で意図的に不安定にすることの方が良いというのが私たちの哲学です。

エラーのテキストが常に変化するのはなぜですか?

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

エラーが 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 によって生成されるコードをカスタマイズできますか?

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