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.Equalproperly with protobuf messages, use protocmp.Transform
- To use