Current behavior
In buffa-codegen/src/oneof.rs:
pub(crate) fn is_boxed_variant(ty: Type) -> bool {
matches!(ty, Type::TYPE_MESSAGE | Type::TYPE_GROUP)
}
Every message-typed oneof variant is unconditionally wrapped in ::buffa::alloc::boxed::Box<T>:
pub enum Body {
Foo(::buffa::alloc::boxed::Box<FooBody>),
Bar(::buffa::alloc::boxed::Box<BarBody>),
// ...
}
The doc comment near is_boxed_variant notes this is "consistent with MessageField<T> being Option<Box<T>> for singular message fields." Sound rationale, but no opt-out exists today.
Pain point
In code that constructs oneof variants frequently — test fixtures, builders, codegen consumers — every site becomes:
parent.body = Some(message_data::Body::Foo(
::buffa::alloc::boxed::Box::new(FooBody { ... })
));
The From<T> impl buffa-codegen emits helps a little (Body::from(foo) → Body::Foo(Box::new(foo))), but it's not always applicable — for example when multiple variants share a Rust type (the codegen explicitly skips From for those, per the comment around line 220 of oneof.rs).
For small messages or hot construction paths, the indirection costs more than it saves: an extra allocation per variant and noisier code at every construction site.
Prior art
prost-build defaulted to not boxing and let callers opt in per-field:
prost_build::Config::new()
.boxed(".my.package.MyMessage.deep_nested_field")
.compile_protos(&[...], &[...])?;
This put the size/indirection tradeoff in the user's hands.
Proposal
Add a config knob — one of:
Option A: global toggle (default keeps current behavior).
Config::new().box_message_oneof_variants(false)
Option B: prost-style per-path opt-out.
Config::new().unbox(".my.package.MyMessage.body.small_variant")
Option C: a single "box only when X" heuristic — e.g. box only if the variant struct is statically known to be ≥ N bytes, where N is configurable.
(A) is simplest and matches Config::preserve_unknown_fields(false)'s style. (B) is more granular and matches the existing prost migration story. Either feels in keeping with the existing Config builder.
Default should probably stay as today (always-box) to preserve the documented "consistent with MessageField<T>" invariant without breaking existing code.
Why this matters
MessageField<T> is Option<Box<T>> because it might be unset; the Box there is paying for absence. A oneof variant is present when matched — there's no absence to encode. The "consistency" argument is therefore a code-shape consistency, not a semantic necessity. For consumers who'd rather take the size hit on the enum to win construction ergonomics, the opt-out is worth having.
Current behavior
In
buffa-codegen/src/oneof.rs:Every message-typed oneof variant is unconditionally wrapped in
::buffa::alloc::boxed::Box<T>:The doc comment near
is_boxed_variantnotes this is "consistent withMessageField<T>beingOption<Box<T>>for singular message fields." Sound rationale, but no opt-out exists today.Pain point
In code that constructs oneof variants frequently — test fixtures, builders, codegen consumers — every site becomes:
The
From<T>implbuffa-codegenemits helps a little (Body::from(foo)→Body::Foo(Box::new(foo))), but it's not always applicable — for example when multiple variants share a Rust type (the codegen explicitly skipsFromfor those, per the comment around line 220 ofoneof.rs).For small messages or hot construction paths, the indirection costs more than it saves: an extra allocation per variant and noisier code at every construction site.
Prior art
prost-builddefaulted to not boxing and let callers opt in per-field:This put the size/indirection tradeoff in the user's hands.
Proposal
Add a config knob — one of:
Option A: global toggle (default keeps current behavior).
Option B: prost-style per-path opt-out.
Option C: a single "box only when X" heuristic — e.g. box only if the variant struct is statically known to be ≥ N bytes, where N is configurable.
(A) is simplest and matches
Config::preserve_unknown_fields(false)'s style. (B) is more granular and matches the existing prost migration story. Either feels in keeping with the existingConfigbuilder.Default should probably stay as today (always-box) to preserve the documented "consistent with
MessageField<T>" invariant without breaking existing code.Why this matters
MessageField<T>isOption<Box<T>>because it might be unset; theBoxthere is paying for absence. A oneof variant is present when matched — there's no absence to encode. The "consistency" argument is therefore a code-shape consistency, not a semantic necessity. For consumers who'd rather take the size hit on the enum to win construction ergonomics, the opt-out is worth having.