Skip to content

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.

enum Kind {
    KIND_UNKNOWN = 0;   // reserved, explicit
    KIND_A       = 1;
    KIND_B       = 2;
}

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:

enum Kind {
    KIND_A = 0;   // ⚠️ implicit default was a real value
    KIND_B = 1;
    KIND_C = 2;   // new
}

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:

enum Kind {
    KIND_UNKNOWN = 0;
    KIND_A       = 1;
    KIND_B       = 2;
    KIND_C       = 3;
}

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 just UNKNOWN).
  • _UNSPECIFIED or _UNKNOWN are both common; pick one across the team. _UNKNOWN is slightly better when the semantic is "I saw a value I don't understand," _UNSPECIFIED when the semantic is "the producer didn't set it."

Seen in

  • sources/2024-09-16-lyft-protocol-buffer-design-principles-and-practicescanonical statement. Lyft Media's protobuf practices post frames _UNKNOWN = 0 as 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.
Last updated · 319 distilled / 1,201 read