CONCEPT Cited by 1 source
Proto3 explicit optional¶
Definition¶
Protobuf 3's three-way history with field presence: proto2 had
required and optional labels; proto3 (initial) dropped both —
every field was implicitly "optional with default," but presence was
not introspectable for singular scalars; proto3 3.15 (February 2021)
reintroduced the optional label on singular scalars to restore
HasField() / .has_X presence checks. The three solutions (from
most to least preferred for new work):
optionallabel (proto3 ≥ 3.15):- Wrapper types (pre-3.15 fallback, still useful where upgrade is expensive):
oneofhack (one-field "oneof" trick):
Why this problem exists¶
In proto3's initial design, scalar fields always initialise to their
type's default on the consumer — 0 for numerics, "" for strings,
false for bool, empty for repeated. A producer that left a field
unset and a producer that set it to 0 generate identical wire bytes.
On the consumer:
This ambiguity breaks multiple common schemas:
- Counters where
0is a legitimate value distinct from "no reading." - Timestamps before the Unix epoch (= negative, but consumers
often compare against
0for "unset"). - Flags where
falseand "not applicable" differ. - Updates — patch semantics where absent means "don't change."
The proto2 required label attempted to prevent this class of bug,
but "it was nearly impossible to safely change a required field to
be optional" without breaking every old client. Removing required
cleaned up that migration cliff but introduced the presence ambiguity
— which 3.15's optional revival addresses.
Timeline¶
| Version | Date | Behaviour |
|---|---|---|
| proto2 | 2008 | required, optional, repeated labels. Presence available on optionals. |
| proto3 | 2016 | required and optional removed. Every singular field is "optional." No presence on scalars. |
| proto3 | 2021-02 (3.15) | optional label reintroduced on singular scalars. HasField() works again. |
Enums in proto3 added a related problem that the UNKNOWN = 0
convention addresses — see concepts/unknown-zero-enum-value.
Proto2 required was a one-way door¶
Per the 2024-09-16 Lyft post:
"The
requiredlabel was enforced strictly by the compiler which proved hugely problematic in the long run, because it was nearly impossible to safely change a required field to be optional."
If a proto2 producer marks X required and ships it, every consumer
depends on X being present. Relaxing X to optional breaks every
consumer's parse. The migration is unbounded-clients × unbounded-time.
Google concluded the right answer was to remove the label from the
language — presence semantics should be modelled via oneof (tagged
union) or wrapper types.
Using .HasField()¶
After optional:
# proto3 ≥ 3.15
if not event_pb.data_c.HasField('payload_size_bytes'):
handle_absent()
elif event_pb.data_c.payload_size_bytes == 0:
handle_zero()
else:
handle_nonzero(event_pb.data_c.payload_size_bytes)
The distinction between "absent" and "zero" is now representable.
Lyft's convention¶
Lyft Media adopted protobuf before 3.15 existed, so their legacy
convention is to use google.protobuf.*Value wrappers:
"Since protobufs were adopted at Lyft prior to the introduction of optionals to the language specification, our convention for optional primitive types is to use wrappers from the
google.protobufpackage."
Both wrapper types and the optional label produce the same
observable HasField semantics; wrappers cost slightly more bytes on
the wire (an extra nested-message header) but carry forward cleanly
without a schema migration.
Seen in¶
- sources/2024-09-16-lyft-protocol-buffer-design-principles-and-practices
— canonical statement on the wiki. Lyft Media's post walks
through the historical context (proto2
required→ proto3 drop → 3.15 reintroduction), names the bug (absent vs default indistinguishable), shows both fixes (optionallabel + wrapper types), and discloses Lyft's specific choice to standardise on wrappers for pre-3.15 codebases.
Related¶
- systems/protobuf — the schema system
- concepts/backward-compatibility — proto3's removal of
requiredis a canonical instance of the "required labels are a one-way door" rule - concepts/unknown-zero-enum-value — sibling default-value
problem on enums (enums get
0=UNKNOWN, primitives getoptionalor wrappers) - concepts/clarity-over-efficiency-in-protocol-design —
optionaltrades a few wire bytes for presence semantics; clarity wins - patterns/protobuf-validation-rules — PGV / protovalidate rules
compose cleanly with wrapper types and with
optionalfields