Decode profiles & unknown fields
Arena-only features that tailor decoding to what this consumer needs, without touching the schema. Back to the README; the arena model itself is in arena.md.
Unknown fields
By default, fields not in your schema (a newer producer’s field, or a proto2 extension) are skipped
and dropped. To detect (not recover) that unknowns were present, reserve a per-message “saw an
unknown field” flag, exposed as has_unknown_fields():
--unknown-presentreserves it on every message.--unknown=<pkg.Msg>(repeatable), or anunknown-fields <pkg.Msg>line in a decode profile, reserves it on one message - so you pay the bit only where you check it.
The selection is part of the decode profile: it folds into the profile identity - see below.
Decode profiles: drop, raw, and unknown-fields
The schema says what can be on the wire; a decode profile says what this consumer does with
it. Choose, per field (pkg.Msg.field), per type (pkg.Msg, covering
every field of that message type), or per message (for unknown-fields).
drop - no storage, no accessor
No decode work beyond wire-validated skipping. Reading a dropped field is a compile error,
not a silent default. (Dropping a required field is rejected.)
unknown-fields - reserve one message’s detection bit
Reserves that message’s has_unknown_fields() bit (see Unknown fields).
Unlike drop/raw it names a message directly, not a field or a field’s type; an enum or a
field name is an error.
raw - keep a sub-message as bytes, decode on demand
For message-typed fields (groups included): the sub-message’s payload is borrowed as a
ByteView into the input instead of a materialized tree; a repeated field becomes a
StringArrayView, one payload per element. Each view is exactly what the field type’s own
decode() accepts, so the tree is built only if you ask. Typical targets: a
huge or rarely-read sub-message, or a million-element repeated field read element-wise.
- Presence: a singular
rawaccessor returnsstd::optional<ByteView>, carrying presence just as theconst T*does; arequiredfield (proto2, or editionsLEGACY_REQUIRED) has no presence to carry and returns a bareByteView. - Decode semantics are otherwise unchanged:
requiredvalidation, duplicate-singular rejection. - What can’t go raw: scalars, strings and enums (no payload a later
decode()could consume) and maps (their entry type is generated internals). To defer a huge packed scalar array, wrap it in a sub-message schema-side, or walk it with the streaming decoder.
Writing a profile: --field-modes and the inline flags
Profiles come from a file (one drop <name> / raw <name> / unknown-fields <message> per line, #
comments, an optional name <identifier> line) via --field-modes=<file>, or inline via
--drop=<name> / --raw=<name> / --unknown=<message> (and --unknown-present for every message). A
field-level entry beats a type-level entry; field modes do not apply inside a oneof. Profile entries
resolve against every schema the invocation generates, as one batch, so a global profile works by
listing (or PROTOS-listing, in CMake) every schema it spans in one generation; a name unknown
across the whole batch is still a hard error.
# lean.modes - this consumer never reads sides, and reads origin only on demand
name lean
drop demo.Shape.sides
raw demo.Shape.origin
namespace demo = rp::arena::demo; // arena types live under rp::arena
const demo::Shape* s = demo::Shape::decode(bytes, arena); // s->sides() does not compile
if (s->origin()) { // the Point payload (borrowed from the input)
const demo::Point* p = demo::Point::decode(*s->origin(), arena); // deferred: only now
}
Profiles change the generated types
A profile changes the generated types, so you still write rp::arena::demo::Shape, but two TUs generated
under different profiles (including differing only in which messages reserve the unknown-fields bit)
hold distinct types and fail to link rather than silently exchanging mismatched layouts.
That guard is C++ name mangling, so it reaches exactly as far as mangling does - a function’s
parameters, not its return type. Shape make(); compiled under two profiles links, and the
caller reads the result with the layout it was built against. A signature whose only generated types
are enums is unguarded for a different reason: enums are one type across profiles by design, shared
through the common header. Cross a profile boundary through a parameter, or keep it inside one TU.
Don’t forward-declare generated types yourself: under a profile,
namespace rp::arena::demo { class Shape; } declares a different class. See
examples/consumer/lean_main.cpp for the full pattern.