Expand description
Hybrid serialization tail for #[hopper::state(dynamic_tail = T)].
Closes Hopper Safety Audit innovation I5 (“Hybrid serialization”). The rationale from the audit (page 14):
Lets Hopper own the fixed-layout hot path while still supporting a dynamic tail for vectors, strings, and optional metadata.
§Wire format
After the layout’s fixed body (offset TYPE_OFFSET + WIRE_SIZE), the
tail is encoded as:
[ len: u32 LE ] [ payload: len bytes ]The fixed-body fast path remains fully zero-copy. code that never
touches the tail pays zero overhead. Tail access is explicit
(tail_read::<T>() / tail_write::<T>()), which is why the tail
is not zero-copy: the typed representation is reconstructed on
read and serialized on write.
§Canonical tail encoding (TailCodec)
TailCodec is a minimal Borsh-subset serializer:
- integers: native little-endian
[u8; N]: raw bytes, fixed width- bounded byte/string payloads: program-defined length prefix + bytes
Option<T>: 1-byte tag (0 = None, 1 = Some) + inner payload
Programs that need richer types (bounded strings, bounded vectors,
custom structs) implement TailCodec themselves; the framework does not
force a derive or pull Vec / String into the no-alloc runtime surface.
Traits§
- Tail
Codec - Canonical serializer for dynamic-tail payloads.
Functions§
- read_
tail - Decode the tail as
T: TailCodec, checking that the encoded length exactly matches the u32 prefix. Extra bytes beyondT’s decode are a malformed-encoding signal. - read_
tail_ len - Read the tail’s u32-LE length prefix.
- tail_
payload - Return a slice referencing just the tail payload bytes (excluding the 4-byte length prefix). Length-bounded by the u32 prefix.
- write_
tail - Encode
tailinto the account’s tail slot, rewriting the u32 length prefix. ReturnsAccountDataTooSmallwhen the existing account byte buffer can’t fit the encoded payload. in that case the caller shouldreallocfirst.