DynamicMessage::from_message is the only road from a generated Message value to reflection, and it has two costs a caller cannot avoid:
- it encodes the whole message to a
Vec<u8> and decodes it again against the descriptor, so a library that evaluates over a request struct it already holds pays an encode plus a full decode on every call;
- it takes a
MessageIndex the caller has to resolve by hand from the pool (strip a leading dot, look the full name up), and it panics on a descriptor mismatch instead of returning an error, so a pool that was built from a different .proto revision than the generated code turns into a process abort rather than a DecodeError.
pub fn from_message<M: Message>(msg: &M, pool: Arc<DescriptorPool>, msg_idx: MessageIndex) -> Self {
let bytes = msg.encode_to_vec();
Self::decode(pool, msg_idx, &bytes)
.expect("generated message must round-trip through its own descriptor")
}
What would close the gap, in increasing order of ambition:
- A fallible variant,
try_from_message, that returns the DecodeError and leaves the panicking one as a thin wrapper (or deprecates it).
- Index resolution from the type itself: with
M: Message + MessageName the full name is M::PACKAGE + M::NAME, so the caller should not need to look up the MessageIndex at all — try_from_message(msg, pool) with a DecodeError/NotFound when the pool lacks the type.
- A road that does not re-encode: either a generated-code reflection hook (each generated message can walk its own fields against its descriptor and populate the dynamic field map directly), or a borrowed reflective view over
&M that answers field-by-field reads without materialising a DynamicMessage. This is what makes reflection over a message the host already owns cheap enough to use per request.
Items 1 and 2 are small and additive. Item 3 is the real gap and probably wants a design note first; happy to sketch one if there is appetite.
DynamicMessage::from_messageis the only road from a generatedMessagevalue to reflection, and it has two costs a caller cannot avoid:Vec<u8>and decodes it again against the descriptor, so a library that evaluates over a request struct it already holds pays an encode plus a full decode on every call;MessageIndexthe caller has to resolve by hand from the pool (strip a leading dot, look the full name up), and it panics on a descriptor mismatch instead of returning an error, so a pool that was built from a different.protorevision than the generated code turns into a process abort rather than aDecodeError.What would close the gap, in increasing order of ambition:
try_from_message, that returns theDecodeErrorand leaves the panicking one as a thin wrapper (or deprecates it).M: Message + MessageNamethe full name isM::PACKAGE+M::NAME, so the caller should not need to look up theMessageIndexat all —try_from_message(msg, pool)with aDecodeError/NotFoundwhen the pool lacks the type.&Mthat answers field-by-field reads without materialising aDynamicMessage. This is what makes reflection over a message the host already owns cheap enough to use per request.Items 1 and 2 are small and additive. Item 3 is the real gap and probably wants a design note first; happy to sketch one if there is appetite.