Go Opaque API: 手動移行

Opaque API への手動移行について説明します。

Opaque API は、Go プログラミング言語向けの Protocol Buffers 実装の最新バージョンです。古いバージョンは現在 Open Struct API と呼ばれています。詳細は、Go Protobuf: Releasing the Opaque API のブログ記事をご覧ください。

これは、Go Protobuf の使用方法を古い Open Struct API から新しい Opaque API に移行するためのユーザーガイドです。

生成コードガイドで詳細を説明しています。このガイドでは、古い API と新しい API を並べて比較しています。

メッセージの構築

次のように定義された protobuf メッセージがあるとします

message Foo {
  uint32 uint32 = 1;
  bytes bytes = 2;
  oneof union {
    string    string = 4;
    MyMessage message = 5;
  }
  enum Kind {  };
  Kind kind = 9;
}

リテラル値からこのメッセージを構築する方法の例を次に示します

Open Struct API (旧)Opaque API (新)
m := &pb.Foo{
  Uint32: proto.Uint32(5),
  Bytes:  []byte("hello"),
}
m := pb.Foo_builder{
  Uint32: proto.Uint32(5),
  Bytes:  []byte("hello"),
}.Build()

ご覧のとおり、ビルダー構造体を使用すると、Open Struct API (旧) と Opaque API (新) の間でほぼ 1:1 の変換が可能です。

一般に、可読性のためにビルダーを使用することを推奨します。ホットな内部ループで Protobuf メッセージを作成するような稀なケースでのみ、ビルダーの代わりにセッターを使用する方が望ましい場合があります。詳細については、Opaque API FAQ: ビルダーとセッターのどちらを使用すべきか? を参照してください。

上記の例の例外は、oneof を扱う場合です。Open Struct API (旧) は各 oneof ケースにラッパー構造体型を使用しますが、Opaque API (新) は oneof フィールドを通常のメッセージフィールドのように扱います

Open Struct API (旧)Opaque API (新)
m := &pb.Foo{
  Uint32: myScalar,  // could be nil
  Union:  &pb.Foo_String{myString},
  Kind:   pb.Foo_SPECIAL_KIND.Enum(),
}
m := pb.Foo_builder{
  Uint32: myScalar,
  String: myString,
  Kind:   pb.Foo_SPECIAL_KIND.Enum(),
}.Build()

oneof ユニオンに関連付けられた Go 構造体フィールドのセットでは、1 つのフィールドのみを格納できます。複数の oneof ケースフィールドが格納された場合、最後のもの ( .proto ファイルでのフィールド宣言順) が優先されます。

スカラフィールド

スカラフィールドで定義されたメッセージがあるとします

message Artist {
  int32 birth_year = 1;
}

Go がスカラ型 (bool, int32, int64, uint32, uint64, float32, float64, string, []byte, および enum) を使用する Protobuf メッセージフィールドには、Get および Set アクセサメソッドがあります。明示的な存在を持つフィールドには、Has および Clear メソッドも含まれます。

birth_year という名前の int32 型のフィールドの場合、次のアクセサメソッドが生成されます

func (m *Artist) GetBirthYear() int32
func (m *Artist) SetBirthYear(v int32)
func (m *Artist) HasBirthYear() bool
func (m *Artist) ClearBirthYear()

Get はフィールドの値を返します。フィールドが設定されていないか、メッセージレシーバーが nil の場合、デフォルト値を返します。デフォルト値は、デフォルトオプションで明示的に設定されていない限り、ゼロ値です。

Set は指定された値をフィールドに格納します。nil メッセージレシーバーで呼び出されるとパニックになります。

バイトフィールドの場合、nil の []byte で Set を呼び出すと、設定されていると見なされます。たとえば、直後に Has を呼び出すと true を返します。直後に Get を呼び出すと、長さゼロのスライス (nil または空のスライスのいずれか) が返されます。ユーザーは、存在を判断するために Has を使用し、Get が nil を返すかどうかに依存すべきではありません。

Has はフィールドが設定されているかどうかを報告します。nil メッセージレシーバーで呼び出されると false を返します。

Clear はフィールドをクリアします。nil メッセージレシーバーで呼び出されるとパニックになります。

文字列フィールドを使用したコードスニペットの例

Open Struct API (旧)Opaque API (新)
// Getting the value.
s := m.GetBirthYear()

// Setting the field.
m.BirthYear = proto.Int32(1989)

// Check for presence.
if s.BirthYear != nil {  }

// Clearing the field.
m.BirthYear = nil
// Getting the field value.
s := m.GetBirthYear()

// Setting the field.
m.SetBirthYear(1989)

// Check for presence.
if m.HasBirthYear() {  }

// Clearing the field
m.ClearBirthYear()

メッセージフィールド

メッセージ型のフィールドで定義されたメッセージがあるとします

message Band {}

message Concert {
  Band headliner = 1;
}

メッセージ型の Protobuf メッセージフィールドには、GetSetHas、および Clear メソッドがあります。

headliner という名前のメッセージ型フィールドの場合、次のアクセサメソッドが生成されます

func (m *Concert) GetHeadliner() *Band
func (m *Concert) SetHeadliner(*Band)
func (m *Concert) HasHeadliner() bool
func (m *Concert) ClearHeadliner()

Get はフィールドの値を返します。設定されていない場合、または nil メッセージレシーバーで呼び出された場合は nil を返します。Get が nil を返すかどうかをチェックすることは、Has が false を返すかどうかをチェックすることと同じです。

Set は指定された値をフィールドに格納します。nil メッセージレシーバーで呼び出されるとパニックになります。nil ポインタで Set を呼び出すことは、Clear を呼び出すことと同じです。

Has はフィールドが設定されているかどうかを報告します。nil メッセージレシーバーで呼び出されると false を返します。

Clear はフィールドをクリアします。nil メッセージレシーバーで呼び出されるとパニックになります。

コードスニペットの例

Open Struct API (旧)Opaque (新)
// Getting the value.
b := m.GetHeadliner()

// Setting the field.
m.Headliner = &pb.Band{}

// Check for presence.
if s.Headliner != nil {  }

// Clearing the field.
m.Headliner = nil
// Getting the value.
s := m.GetHeadliner()

// Setting the field.
m.SetHeadliner(&pb.Band{})

// Check for presence.
if m.HasHeadliner() {  }

// Clearing the field
m.ClearHeadliner()

繰り返しフィールド

繰り返しメッセージ型フィールドで定義されたメッセージがあるとします

message Concert {
  repeated Band support_acts = 2;
}

繰り返しフィールドには、Get および Set メソッドがあります。

Get はフィールドの値を返します。フィールドが設定されていないか、メッセージレシーバーが nil の場合、nil を返します。

Set は指定された値をフィールドに格納します。nil メッセージレシーバーで呼び出されるとパニックになります。Set は、提供されたスライスヘッダーのコピーを格納します。スライスコンテンツへの変更は、繰り返しフィールドで観測可能です。したがって、空のスライスで Set が呼び出されると、直後に Get を呼び出すと、同じスライスが返されます。ワイヤーまたはテキストマーシャリング出力の場合、渡された nil スライスは空のスライスと区別できません。

Concert メッセージの support_acts という名前の繰り返しメッセージ型フィールドの場合、次のアクセサメソッドが生成されます

func (m *Concert) GetSupportActs() []*Band
func (m *Concert) SetSupportActs([]*Band)

コードスニペットの例

Open Struct API (旧)Opaque API (新)
// Getting the entire repeated value.
v := m.GetSupportActs()

// Setting the field.
m.SupportActs = v

// Get an element in a repeated field.
e := m.SupportActs[i]

// Set an element in a repeated field.
m.SupportActs[i] = e

// Get the length of a repeated field.
n := len(m.GetSupportActs())

// Truncate a repeated field.
m.SupportActs = m.SupportActs[:i]

// Append to a repeated field.
m.SupportActs = append(m.GetSupportActs(), e)
m.SupportActs = append(m.GetSupportActs(), v...)

// Clearing the field.
m.SupportActs = nil
// Getting the entire repeated value.
v := m.GetSupportActs()

// Setting the field.
m.SetSupportActs(v)

// Get an element in a repeated field.
e := m.GetSupportActs()[i]

// Set an element in a repeated field.
m.GetSupportActs()[i] = e

// Get the length of a repeated field.
n := len(m.GetSupportActs())

// Truncate a repeated field.
m.SetSupportActs(m.GetSupportActs()[:i])

// Append to a repeated field.
m.SetSupportActs(append(m.GetSupportActs(), e))
m.SetSupportActs(append(m.GetSupportActs(), v...))

// Clearing the field.
m.SetSupportActs(nil)

マップ

マップ型フィールドで定義されたメッセージがあるとします

message MerchBooth {
  map<string, MerchItems> items = 1;
}

マップフィールドには、Get および Set メソッドがあります。

Get はフィールドの値を返します。フィールドが設定されていないか、メッセージレシーバーが nil の場合、nil を返します。

Set は指定された値をフィールドに格納します。nil メッセージレシーバーで呼び出されるとパニックになります。Set は、提供されたマップ参照のコピーを格納します。提供されたマップへの変更は、マップフィールドで観測可能です。

MerchBooth メッセージの items という名前のマップフィールドの場合、次のアクセサメソッドが生成されます

func (m *MerchBooth) GetItems() map[string]*MerchItem
func (m *MerchBooth) SetItems(map[string]*MerchItem)

コードスニペットの例

Open Struct API (旧)Opaque API (新)
// Getting the entire map value.
v := m.GetItems()

// Setting the field.
m.Items = v

// Get an element in a map field.
v := m.Items[k]

// Set an element in a map field.
// This will panic if m.Items is nil.
// You should check m.Items for nil
// before doing the assignment to ensure
// it won't panic.
m.Items[k] = v

// Delete an element in a map field.
delete(m.Items, k)

// Get the size of a map field.
n := len(m.GetItems())

// Clearing the field.
m.Items = nil
// Getting the entire map value.
v := m.GetItems()

// Setting the field.
m.SetItems(v)

// Get an element in a map field.
v := m.GetItems()[k]

// Set an element in a map field.
// This will panic if m.GetItems() is nil.
// You should check m.GetItems() for nil
// before doing the assignment to ensure
// it won't panic.
m.GetItems()[k] = v

// Delete an element in a map field.
delete(m.GetItems(), k)

// Get the size of a map field.
n := len(m.GetItems())

// Clearing the field.
m.SetItems(nil)

Oneof

oneof ユニオンのグループごとに、メッセージに WhichHas、および Clear メソッドがあります。また、そのユニオン内の各 oneof ケースフィールドには、GetSetHas、および Clear メソッドがあります。

oneof avatar 内に image_urlimage_data の oneof フィールドを持つメッセージが次のように定義されているとします

message Profile {
  oneof avatar {
    string image_url = 1;
    bytes image_data = 2;
  }
}

この oneof に対して生成される Opaque API は次のようになります

func (m *Profile) WhichAvatar() case_Profile_Avatar {  }
func (m *Profile) HasAvatar() bool {  }
func (m *Profile) ClearAvatar() {  }

type case_Profile_Avatar protoreflect.FieldNumber

const (
  Profile_Avatar_not_set_case case_Profile_Avatar = 0
  Profile_ImageUrl_case case_Profile_Avatar = 1
  Profile_ImageData_case case_Profile_Avatar = 2
)

Which は、フィールド番号を返すことで、どのケースフィールドが設定されているかを報告します。何も設定されていない場合、または nil メッセージレシーバーで呼び出された場合は 0 を返します。

Has は、oneof 内のいずれかのフィールドが設定されているかどうかを報告します。nil メッセージレシーバーで呼び出されると false を返します。

Clear は、oneof 内の現在設定されているケースフィールドをクリアします。nil メッセージレシーバーで呼び出されるとパニックになります。

各 oneof ケースフィールドに対して生成される Opaque API は次のようになります

func (m *Profile) GetImageUrl() string {  }
func (m *Profile) GetImageData() []byte {  }

func (m *Profile) SetImageUrl(v string) {  }
func (m *Profile) SetImageData(v []byte) {  }

func (m *Profile) HasImageUrl() bool {  }
func (m *Profile) HasImageData() bool {  }

func (m *Profile) ClearImageUrl() {  }
func (m *Profile) ClearImageData() {  }

Get はケースフィールドの値を返します。ケースフィールドが設定されていない場合、または nil メッセージレシーバーで呼び出された場合は、ゼロ値を返します。

Set は指定された値をケースフィールドに格納します。また、oneof ユニオン内で以前に設定されていたケースフィールドも暗黙的にクリアします。oneof メッセージケースフィールドを nil 値で Set を呼び出すと、フィールドは空のメッセージに設定されます。nil メッセージレシーバーで呼び出されるとパニックになります。

Has はケースフィールドが設定されているかどうかを報告します。nil メッセージレシーバーで呼び出されると false を返します。

Clear はケースフィールドをクリアします。以前に設定されていた場合、oneof ユニオンもクリアされます。oneof ユニオンが異なるフィールドに設定されている場合、oneof ユニオンはクリアされません。nil メッセージレシーバーで呼び出されるとパニックになります。

コードスニペットの例

Open Struct API (旧)Opaque API (新)
// Getting the oneof field that is set.
switch m.GetAvatar().(type) {
case *pb.Profile_ImageUrl:
   = m.GetImageUrl()
case *pb.Profile_ImageData:
   = m.GetImageData()
}

// Setting the fields.
m.Avatar = &pb.Profile_ImageUrl{"http://"}
m.Avatar = &pb.Profile_ImageData{img}

// Checking whether any oneof field is set
if m.Avatar != nil {  }

// Clearing the field.
m.Avatar = nil

// Checking if a specific field is set.
_, ok := m.GetAvatar().(*pb.Profile_ImageUrl)
if ok {  }

// Clearing a specific field
_, ok := m.GetAvatar().(*pb.Profile_ImageUrl)
if ok {
  m.Avatar = nil
}

// Copy a oneof field.
m.Avatar = src.Avatar
// Getting the oneof field that is set.
switch m.WhichAvatar() {
case pb.Profile_ImageUrl_case:
   = m.GetImageUrl()
case pb.Profile_ImageData_case:
   = m.GetImageData()
}

// Setting the fields.
m.SetImageUrl("http://")
m.SetImageData([]byte("…"))

// Checking whether any oneof field is set
if m.HasAvatar() {  }

// Clearing the field.
m.ClearAvatar()

// Checking if a specific field is set.
if m.HasImageUrl() {  }

// Clearing a specific field.
m.ClearImageUrl()

// Copy a oneof field
switch src.WhichAvatar() {
case pb.Profile_ImageUrl_case:
  m.SetImageUrl(src.GetImageUrl())
case pb.Profile_ImageData_case:
  m.SetImageData(src.GetImageData())
}

リフレクション

Open Struct API から移行すると、Go reflect パッケージを使用して構造体フィールドとタグにアクセスする proto メッセージ型を使用するコードは機能しなくなります。コードは protoreflect を使用するように移行する必要があります

一部の一般的なライブラリは、内部で Go reflect を使用しています。例を次に示します。