Go Opaque API: 手動マイグレーション

Opaque API への手動マイグレーションについて説明します。

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

これは、古い Open Struct API から新しい Opaque API へ Go Protobuf の利用箇所をマイグレーションするためのユーザーガイドです。

コード生成ガイド に詳細が記載されています。このガイドでは、古い 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 を呼び出すと、ゼロ長の slice (nil または空の slice のいずれか) が返されます。ユーザーは、存在を確認するために 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;
}

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

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

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

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

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 は、指定されたマップ参照のコピーを格納します。指定されたマップへの変更は、マップフィールドで確認できます。

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

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)

Oneofs

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

たとえば、次のように oneof avatar に oneof フィールド image_urlimage_data が定義されたメッセージがあるとします。

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 ユニオン内で以前に設定されていたケースフィールドを暗黙的にクリアします。nil 値で oneof メッセージケースフィールドで 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 から移行すると、proto メッセージ型で Go reflect パッケージを使用して構造体フィールドとタグにアクセスするコードは機能しなくなります。コードは protoreflect を使用するように移行する必要があります。

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