CONCEPT Cited by 1 source
Unknown zero enum value¶
Definition¶
Convention of reserving the 0-th element of every
protobuf enum as an explicit UNKNOWN / UNSPECIFIED value so
that old consumers reading messages produced by newer schemas (which
omit the field, or set it to a value older code doesn't recognise)
interpret the absence unambiguously rather than as a misleading real
value.
Why this matters¶
Proto3 enums must declare a value for 0 — the compiler requires
it — and any enum-typed field that is unset on the wire decodes to 0
on the consumer side. If 0 is a legitimate enum value, consumers
cannot distinguish "the producer didn't set this field" from "the
producer set it to Kind.A because A was historically the first
entry."
When a new value is later added to the enum:
a new producer running this schema emits Kind.C = 2 on the wire. An
old consumer that predates this addition, upon seeing 2, may follow
whatever "default" path its code had for 0 — silently treating C as
A. The bug is on the unhappy path for new + old code interacting, and
it's invisible on either side in isolation.
With the UNKNOWN = 0 convention:
old consumers see unset-or-unknown values as a visible KIND_UNKNOWN
and can branch — refuse the message, log a warning, degrade to a
default path — deliberately. The enum also documents itself: greppable
across codebases, explicit about the existence of a sentinel.
Combine with validation¶
Proto3 enums are "open" — wire-level integers outside the declared set are accepted at parse time. To close the set, pair the convention with a protoc-gen-validate / protovalidate rule:
Kind kind = 3 [(validate.rules).enum = {
defined_only: true,
not_in: [0] // reject UNKNOWN explicitly
}];
defined_only: true rejects values outside the declared set;
not_in: [0] rejects the sentinel so production messages must carry
a real enum value. See patterns/protobuf-validation-rules.
Naming conventions¶
Canonical prefixes (see Google's proto style guide and the Lyft post):
- Full enum name as the prefix to avoid clashes in C++ scope
(
KIND_UNKNOWN, not justUNKNOWN). _UNSPECIFIEDor_UNKNOWNare both common; pick one across the team._UNKNOWNis slightly better when the semantic is "I saw a value I don't understand,"_UNSPECIFIEDwhen the semantic is "the producer didn't set it."
Seen in¶
- sources/2024-09-16-lyft-protocol-buffer-design-principles-and-practices
— canonical statement. Lyft Media's protobuf practices post frames
_UNKNOWN = 0as the first practice after the principles (clarity, extensibility), with the exact before/after example shown. Pairs explicitly with(validate.rules).enum.not_in: [0]to close the open-enum loophole.
Related¶
- systems/protobuf — the schema system this convention applies to
- systems/protoc-gen-validate — validation plugin that enforces it
- concepts/backward-compatibility — the property this defends
- concepts/schema-evolution — the axis enum-growth lives on
- concepts/proto3-explicit-optional — sibling default-value problem on primitives
- patterns/protobuf-validation-rules — the validation pattern
- patterns/oneof-over-enum-plus-field — a more aggressive fix that eliminates the discriminator enum entirely