Background
Historically, proto2 followed explicit presence for all singular fields. Proto3 exposed only no-presence semantics for basic types (numeric, string, bytes, enums), with the exception of message-typed fields andoneof members, which always track presence. Since release 3.15, proto3 also supports explicit presence for basic types via the optional keyword.
Presence disciplines
Presence disciplines define how the translation between the API and the serialized wire format works. No presence (implicit):- Default values are not serialized onto the wire.
- Default values are not merged-from during
MergeFrom. - To “clear” a field, set it to its default value.
- An API user cannot distinguish between “field was set to default” and “field was never set”.
- Explicitly-set values are always serialized, including default values.
- Unset fields are never merged-from.
- Explicitly-set fields — including default values — are merged-from.
- A generated
has_foo()method indicates whether the field has been set. - A generated
clear_foo()method unsets the field.
Presence in proto2 APIs
In proto2, all singular fields track presence explicitly. The generated API includeshas_foo() and clear_foo() methods for every singular field:
| Field type | Explicit presence |
|---|---|
| Singular numeric (int or float) | Yes |
| Singular enum | Yes |
| Singular string or bytes | Yes |
| Singular message | Yes |
| Repeated | No |
| Oneof | Yes |
| Map | No |
Oneof fields in proto2
Foroneof fields, the generated API includes a case method indicating which field (if any) is set:
has_result()— whether any oneof member is setresult_case()— which member is currently sethas_error_code(),has_data()— member-level hazzersclear_result()— clears the active member
Presence in proto3 APIs
Withoutoptional, proto3 basic-type fields have no presence. Message-typed fields and oneof members always track presence:
| Field type | optional? | Explicit presence |
|---|---|---|
| Singular numeric (int or float) | No | No |
| Singular enum | No | No |
| Singular string or bytes | No | No |
| Singular numeric (int or float) | Yes | Yes |
| Singular enum | Yes | Yes |
| Singular string or bytes | Yes | Yes |
| Singular message | Either | Yes |
| Repeated | N/A | No |
| Oneof | N/A | Yes |
| Map | N/A | No |
Enabling explicit presence in proto3
To add explicit presence to a proto3 basic-type field, add theoptional keyword:
optional in proto3 has been enabled by default since protoc v3.15.0. Earlier releases required the --experimental_allow_proto3_optional flag.
Semantic differences
The behavior difference between the two disciplines is visible when the default value is set:foo. When Client A sets foo = 0 (the default value) and sends to Client B, Client B’s no-presence implementation will not serialize that default value. When the message returns to Client A, has_foo() returns false even though Client A set it. This is a lossy round-trip.
Generated API examples
- C++
- Java
- Python
- Go
- C#
- JavaScript
- Ruby
No presence:Explicit presence:
Considerations for merging
Under no-presence rules, it is impossible forMergeFrom to merge a default value. Default values are skipped, so a patch message cannot represent “set this field to zero”. If you need to patch a field to its default value, you must use an external mechanism like FieldMask.
Under explicit presence, all explicitly-set values — including defaults — are merged into the target. A field set to zero in a patch message will correctly overwrite the target’s non-zero value.
Considerations for change compatibility
Changing a field between explicit and no presence is binary-compatible at the wire level. However, the serialized representation changes:- A sender using explicit presence will serialize default values; a receiver using no presence will not preserve them across a round-trip.
- A sender using no presence will never serialize default values; a receiver using explicit presence will see those fields as absent.
Presence in editions
Editions replace theoptional keyword with the features.field_presence feature flag:
optional keyword is still accepted in editions as a shorthand for [features.field_presence = EXPLICIT]. See proto editions for details on the full feature set.