Background
Linkable is the byte-oriented codec seam for connector links:
to_bytes(&self) -> Result<Vec<u8>, String> and
from_bytes(&[u8]) -> Result<Self, String> (aimdb-data-contracts/src/lib.rs:252).
The .linked_to(url) verb wires the outbound path straight onto it —
with_serializer(|_, v| v.to_bytes()...) (aimdb-data-contracts/src/linkable.rs:41).
Because to_bytes returns an owned Vec<u8>, every outbound message allocates a
fresh heap buffer, then the connector copies it into the transport. On a no_std /
embedded target (Embassy, thumbv7em-none-eabihf) the connector typically already
owns a bounded, preallocated TX buffer, so that per-message allocation is exactly
the heap traffic we otherwise design out. postcard makes this avoidable —
emit_linkable_postcard (aimdb-codegen/src/rust.rs:996) currently calls
postcard::to_allocvec, but postcard::to_slice serializes with no intermediate
Vec at all.
Proposal
Add an optional encode-into-slice method to Linkable, with a default impl so
existing implementations keep compiling unchanged:
/// Serialize into a caller-provided buffer, returning bytes written.
/// Default impl falls back to `to_bytes()` + copy.
fn encode_into(&self, buf: &mut [u8]) -> Result<usize, CodecError> {
let bytes = self.to_bytes().map_err(CodecError::from)?;
if bytes.len() > buf.len() {
return Err(CodecError::BufferTooSmall);
}
buf[..bytes.len()].copy_from_slice(&bytes);
Ok(bytes.len())
}
- Codegen emits a true zero-alloc override (
postcard::to_slice) for
serialization = "postcard" records.
.linked_to prefers encode_into into the connector's transport buffer when
one is exposed, falling back to to_bytes otherwise.
This should be done together with the CodecError alignment already recorded
in design-041 §7 (docs/design/041-data-contracts-integration.md:378): today
Linkable's String error is dropped lossily to SerializeError::InvalidData
(aimdb-data-contracts/src/linkable.rs:44). Both changes touch the same
Linkable ↔ connector-serializer signatures and both remove an embedded
allocation, so they belong in one pass rather than two.
Why it's interesting
- Removes the per-message heap allocation on the embedded outbound hot path.
- postcard already supports
to_slice, so the win is real with no new dependency.
- Backwards-compatible: the default impl means no existing
Linkable impl breaks.
Constraints / risks
- Requires settling
CodecError's shape first (coordinate with design-041 §7);
the seam and the error type move together.
encode_into must stay optional — hand-written JSON/binary impls that don't want
a zero-alloc path keep the default.
Scope
Trait method + codegen override + connector wiring, gated on the CodecError
decision. No change to the self-describing (AimX/MCP/CLI) paths.
Background
Linkableis the byte-oriented codec seam for connector links:to_bytes(&self) -> Result<Vec<u8>, String>andfrom_bytes(&[u8]) -> Result<Self, String>(aimdb-data-contracts/src/lib.rs:252).The
.linked_to(url)verb wires the outbound path straight onto it —with_serializer(|_, v| v.to_bytes()...)(aimdb-data-contracts/src/linkable.rs:41).Because
to_bytesreturns an ownedVec<u8>, every outbound message allocates afresh heap buffer, then the connector copies it into the transport. On a no_std /
embedded target (Embassy,
thumbv7em-none-eabihf) the connector typically alreadyowns a bounded, preallocated TX buffer, so that per-message allocation is exactly
the heap traffic we otherwise design out. postcard makes this avoidable —
emit_linkable_postcard(aimdb-codegen/src/rust.rs:996) currently callspostcard::to_allocvec, butpostcard::to_sliceserializes with no intermediateVecat all.Proposal
Add an optional encode-into-slice method to
Linkable, with a default impl soexisting implementations keep compiling unchanged:
postcard::to_slice) forserialization = "postcard"records..linked_toprefersencode_intointo the connector's transport buffer whenone is exposed, falling back to
to_bytesotherwise.This should be done together with the
CodecErroralignment already recordedin design-041 §7 (
docs/design/041-data-contracts-integration.md:378): todayLinkable'sStringerror is dropped lossily toSerializeError::InvalidData(
aimdb-data-contracts/src/linkable.rs:44). Both changes touch the sameLinkable↔ connector-serializer signatures and both remove an embeddedallocation, so they belong in one pass rather than two.
Why it's interesting
to_slice, so the win is real with no new dependency.Linkableimpl breaks.Constraints / risks
CodecError's shape first (coordinate with design-041 §7);the seam and the error type move together.
encode_intomust stay optional — hand-written JSON/binary impls that don't wanta zero-alloc path keep the default.
Scope
Trait method + codegen override + connector wiring, gated on the
CodecErrordecision. No change to the self-describing (AimX/MCP/CLI) paths.