Skip to content

Protocol Buffers (Go)

Serialization & FormatsSerializationGo

What it is

The Go implementation of Protocol Buffers — compact binary serialisation with a schema, generated types and forward and backward compatibility.

Write a .proto schema, generate Go structs, and marshal to a compact binary form. Field numbers — not names — define the wire format, which is what makes schema evolution safe.

Installation

go get google.golang.org/protobuf

Getting started

The smallest useful thing you can do with it, and what each part means.

Schema and round trip
syntax = "proto3";
package library;
option go_package = "example.com/gen/library";

message Book {
  string id     = 1;
  string title  = 2;
  int32  year   = 3;
  repeated string tags = 4;
}
The numbers are the contract. You may rename a field freely; you must never reuse a number, because old clients still interpret it by position.

Advanced usage

Where the library earns its place over a simpler alternative.

Marshalling and safe evolution
book := &pb.Book{Id: "42", Title: "Dune", Year: 1965}

data, err := proto.Marshal(book)   // compact binary
if err != nil { return err }

var decoded pb.Book
if err := proto.Unmarshal(data, &decoded); err != nil { return err }

// An old binary reading a message with new fields simply ignores them,
// and a new binary reading an old message sees zero values.
if decoded.GetYear() == 0 {
    // proto3 cannot distinguish "absent" from "zero" for scalars.
    // Use optional (proto3 field presence) if that difference matters.
}
The zero-versus-absent ambiguity catches people constantly. Mark the field `optional` when you genuinely need to tell an unset value from a legitimate 0 or empty string.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

cannot parse invalid wire-format data
The bytes are not protobuf, or were produced by an incompatible schema. Confirm both sides use the same field numbers.
A nil message panics on field access
Use the generated GetX() accessors, which return the zero value for a nil receiver.

Best practices

  • Never change or reuse a field number; reserve retired ones with the `reserved` keyword.
  • Use the generated getters — they are nil-safe, unlike direct field access.
  • Mark fields optional when you must distinguish unset from the zero value.
  • Check the schema into the repository and generate in CI so the code cannot drift from it.

Background

Why it exists, and what it was reacting to.

The google.golang.org/protobuf module is the v2 API, a full rewrite that added reflection and a cleaner separation between the wire format and generated code.