Protobuf Editions
protocol-buffers spent years with a fork in it: syntax = "proto2" or syntax = "proto3", two
dialects with different defaults for field presence and repeated-field encoding, and a migration
between them that meant rewriting files. Editions delete the fork. You write edition = "2024"
instead of a syntax line, and every behaviour that used to be implied by the dialect becomes an
explicit, individually settable feature.
Features, and where they can be set
A feature is a named behaviour with a per-edition default, overridable at file, message, field, enum, enum value, oneof, service and method level. Two the docs name:
field_presence—LEGACY_REQUIRED,EXPLICITorIMPLICIT. This is the proto2/proto3 disagreement about whether an unset field is distinguishable from a zero one, demoted from a dialect-wide law to a per-field setting.repeated_field_encoding—PACKEDorEXPANDED, the protobuf-wire-format choice whose default flipped to packed in Edition 2023.
The granularity is the whole point. A behaviour change that once forced a file-by-file syntax migration can now be adopted field by field.
The lifecycle is the actual mechanism
Editions ship "roughly once a year," and a feature moves through a fixed sequence: introduced with a no-op default, then the default changes in a later edition, then a deprecation window of roughly two years, then removal. So the breaking change is never a cliff — it is announced one edition ahead, defaulted the next, and tolerated for two more years.
Crucially, the binary format does not change. Editions files import proto2 and proto3 files and vice versa, and serialized bytes stay compatible across all of it. Editions is a source-language versioning scheme layered over a frozen wire format.
What this is an instance of
The spoke has watched several tools try to change something load-bearing without breaking their
users — git-2-55's incremental work, typescript-7-go-compiler's port, the migration
described in block-monorepo-migration. Editions is the cleanest statement of the pattern yet:
separate the thing that must stay frozen from the thing you want to keep changing, then version
only the second one. The wire format is the contract with every byte ever written; the .proto
language is just source. Freezing the first buys unlimited freedom on the second.
That is also the answer to why Protobuf can afford a yearly release train while its format has been stable for two decades. They are not the same artifact, and editions is the machinery that keeps them apart.
Related
protocol-buffers · protobuf-wire-format · grpc · git-2-55 · block-monorepo-migration · developer-tooling · synthesis