Go Opaque API: Manual Migration
The Opaque API is the latest version of the Protocol Buffers implementation for the Go programming language. The old version is now called Open Struct API. See the Go Protobuf: Releasing the Opaque API blog post for an introduction.
This is a user guide for migrating Go Protobuf usages from the older Open Struct API to the new Opaque API.
Warning
You are looking at the manual migration guide. Typically you’re better off using theopen2opaque
tool to automate the migration. See
Opaque API Migration
instead.The Generated Code Guide provides more detail. This guide compares the old and new API side-by-side.
Message Construction
Suppose there is a protobuf message defined like this:
message Foo {
uint32 uint32 = 1;
bytes bytes = 2;
oneof union {
string string = 4;
MyMessage message = 5;
}
enum Kind { … };
Kind kind = 9;
}
Here is an example of how to construct this message from literal values:
Open Struct API (old) | Opaque API (new) |
|
|
As you can see, the builder structs allow for an almost 1:1 translation between Open Struct API (old) and Opaque API (new).
Generally, prefer using builders for readability. Only in rare cases, like creating Protobuf messages in a hot inner loop, might it be preferable to use setters instead of builders. See the Opaque API FAQ: Should I use builders or setters? for more detail.
An exception to the above example is when working with oneofs: The Open Struct API (old) uses a wrapper struct type for each oneof case, whereas the Opaque API (new) treats oneof fields like regular message fields:
Open Struct API (old) | Opaque API (new) |
|
|
For the set of Go struct fields associated with a oneof union, only one field may be populated. If multiple oneof case fields are populated, the last one (in field declaration order in your .proto file) wins.
Scalar fields
Suppose there is a message defined with a scalar field:
message Artist {
int32 birth_year = 1;
}
Protobuf message fields for which Go uses scalar types (bool, int32, int64,
uint32, uint64, float32, float64, string, []byte, and enum) will have Get
and
Set
accessor methods. Fields with
explicit presence
will also have Has
and Clear
methods.
For a field of type int32
named birth_year
, the following accessor methods
will be generated for it:
func (m *Artist) GetBirthYear() int32
func (m *Artist) SetBirthYear(v int32)
func (m *Artist) HasBirthYear() bool
func (m *Artist) ClearBirthYear()
Get
returns a value for the field. If the field is not set or the message
receiver is nil, it returns the default value. The default value is the
zero value, unless explicitly set with
the default option.
Set
stores the provided value into the field. It panics when called on a nil
message receiver.
For bytes fields, calling Set
with a nil []byte will be considered set. For
example, calling Has
immediately after returns true. Calling Get
immediately
after will return a zero-length slice (can either be nil or empty slice). Users
should use Has
for determining presence and not rely on whether Get
returns
nil.
Has
reports whether the field is populated. It returns false when called on a
nil message receiver.
Clear
clears the field. It panics when called on a nil message receiver.
Example code snippets using a string field in:
Open Struct API (old) | Opaque API (new) |
|
|
Message fields
Suppose there is a message defined with a message-typed field:
message Band {}
message Concert {
Band headliner = 1;
}
Protobuf message fields of type message will have Get
, Set
, Has
and
Clear
methods.
For a message-typed field named headliner
, the following accessor methods will
be generated for it:
func (m *Concert) GetHeadliner() *Band
func (m *Concert) SetHeadliner(*Band)
func (m *Concert) HasHeadliner() bool
func (m *Concert) ClearHeadliner()
Get
returns a value for the field. It returns nil if not set or when called on
a nil message receiver. Checking if Get
returns nil is equivalent to checking
if Has
returns false.
Set
stores the provided value into the field. It panics when called on a nil
message receiver. Calling Set
with a nil pointer is equivalent to calling
Clear
.
Has
reports whether the field is populated. It returns false when called on a
nil message receiver.
Clear
clears the field. It panics when called on a nil message receiver.
Example code snippets
Open Struct API (old) | Opaque (new) |
|
|
Repeated fields
Suppose there is a message defined with a repeated message-typed field:
message Concert {
repeated Band support_acts = 2;
}
Repeated fields will have Get
and Set
methods.
Get
returns a value for the field. It returns nil if the field is not set or
the message receiver is nil.
Set
stores the provided value into the field. It panics when called on a nil
message receiver. Set
will store a copy of the slice header that is provided.
Changes to the slice contents are observable in the repeated field. Hence, if
Set
is called with an empty slice, calling Get
immediately after will return
the same slice. For the wire or text marshaling output, a passed-in nil slice is
indistinguishable from an empty slice.
For a repeated message-typed field named support_acts
on message Concert
,
the following accessor methods will be generated for it:
func (m *Concert) GetSupportActs() []*Band
func (m *Concert) SetSupportActs([]*Band)
Example code snippets
Open Struct API (old) | Opaque API (new) |
|
|
Maps
Suppose there is a message defined with a map-typed field:
message MerchBooth {
map<string, MerchItems> items = 1;
}
Map fields will have Get
and Set
methods.
Get
returns a value for the field. It returns nil if the field is not set or
the message receiver is nil.
Set
stores the provided value into the field. It panics when called on a nil
message receiver. Set
will store a copy of the provided map reference. Changes
to the provided map are observable in the map field.
For a map field named items
on message MerchBooth
, the following accessor
methods will be generated for it:
func (m *MerchBooth) GetItems() map[string]*MerchItem
func (m *MerchBooth) SetItems(map[string]*MerchItem)
Example code snippets
Open Struct API (old) | Opaque API (new) |
|
|
Oneofs
For each oneof union grouping, there will be a Which
, Has
and Clear
method
on the message. There will also be a Get
, Set
, Has
, and Clear
method on
each oneof case field in that union.
Suppose there is a message defined with oneof fields image_url
and
image_data
in oneof avatar
like:
message Profile {
oneof avatar {
string image_url = 1;
bytes image_data = 2;
}
}
The generated Opaque API for this oneof will be:
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
reports which case field is set by returning the field number. It
returns 0 when none are set or when called on a nil message receiver.
Has
reports whether any of the fields within the oneof is set. It returns
false when called on a nil message receiver.
Clear
clears the currently set case field in the oneof. It panics on a nil
message receiver.
The generated Opaque API for each oneof case field will be:
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
returns a value for the case field. It will return the zero value if the
case field is not set or when called on a nil message receiver.
Set
stores the provided value into the case field. It also implicitly clears
the case field that was previously populated within the oneof union. Calling
Set
on a oneof message case field with nil value will set the field to an
empty message. It panics when called on a nil message receiver.
Has
reports whether the case field is set or not. It returns false when called
on a nil message receiver.
Clear
clears the case field. If it was previously set, the oneof union is also
cleared. If the oneof union is set to different field, it will not clear the
oneof union. It panics when called on a nil message receiver.
Example code snippets
Open Struct API (old) | Opaque API (new) |
|
|
Reflection
Code that use Go reflect
package on proto message types to access struct
fields and tags will no longer work when migrating away from the Open Struct
API. Code will need to migrate to use
protoreflect
Some common libraries do use Go reflect
under the hood, examples are:
- encoding/json
- pretty
- cmp
- To use
cmp.Equal
properly with protobuf messages, use protocmp.Transform
- To use