Modeling field unions

One frequent requirement for many kinds of applications is the ability to model unions. A union is a special kind of value that can have multiple representations, all of which point to the same location in memory. The use of shared memory implies that every time we write a value to a particular union field, any attempt to read one of the other union fields will result in garbled data.

The concept of unions extends quite nicely to protocol buffers. If you are working with messages that contain multiple fields where, at most, one field can be set at any given time, you can reduce the amount of required memory by grouping all these fields in a union.

A union definition begins with the oneof keyword, followed by the field name and a list of fields that comprise the union. The following snippet shows a simple example that demonstrates a very common API use case:

message CreateAccountResponse {
string correlation_id = 1;
oneof payload {
Account account = 2;
Error error = 3;
}
}

In this example, all the responses have an associated correlation ID value. However, depending on the outcome of the API call invocation, the response payload will either contain an Account or an Error.

After compiling the aforementioned message definition, the protoc compiler will generate two special types, namely, CreateAccountResponse_Account and CreateAccountResponse_Error, that can be assigned to the Payload field of the CreateAccountResponse type:

type CreateAccountResponse_Account struct {
Account *Account `protobuf:"bytes,1,opt,name=account,proto3,oneof"`
}

type CreateAccountResponse_Error struct {
Error *Error `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
}

To prevent other types from being assigned to the Payload field, the protoc compiler uses an interesting trick: it defines a private interface with an unexported dummy method and arranges it so that only the previous two type definitions implement it:

type isCreateAccountResponse_Payload interface {
isCreateAccountResponse_Payload()
}

func (*CreateAccountResponse_Account) isCreateAccountResponse_Payload() {}
func (*CreateAccountResponse_Error) isCreateAccountResponse_Payload() {}

The protoc compiler specifies the interface mentioned in the preceding code snippet as the type of the Payload field, thus making it a compile-time error to assign any other type to the field. Moreover, to facilitate the retrieval of the union values, the compiler will also generate GetAccount and GetError helpers on the CreateAccountResponse type. These helpers peek into the Payload field's contents and either return the assigned value (Account or Error) or nil if no value of that type has been assigned to the union field.