Expand description
Pod, the canonical runtime-layer “safe to interpret from raw bytes” marker.
Hopper’s typed access primitives (segment_ref, segment_mut,
raw_ref, raw_mut, read_data) all overlay a T on a slice of
account bytes. That overlay is only sound if every bit pattern of
the right size decodes to a valid T and the type has alignment 1
(so the offset within the BPF input buffer is always valid for T).
The Hopper Safety Audit flagged that requiring only T: Copy is too
loose: bool, char, references, and structs with padding are all
Copy + Sized but not safe to overlay on raw bytes. This module
carries the tightened marker.
§Contract
Implementing Pod for a type T asserts all of:
- Every
[u8; size_of::<T>()]byte pattern represents a validT. No “niches”, no enum-discriminant invariants, nobool-style forbidden bit patterns. align_of::<T>() == 1, the type can be read from any byte offset of an account buffer without alignment fault.Tcontains no padding (#[repr(C)]with alignment-1 fields, or#[repr(transparent)]over aPodtype).Tcontains no internal pointers / references, overlay always yields data that’s safe toCopy.
Hopper’s higher-layer macros (#[hopper::state], #[hopper::pod],
hopper_layout!) enforce these conditions at compile time and emit
the derived unsafe impl Pod. Hand-authored layouts opt in via
unsafe impl Pod for MyLayout {}.
§Compile-fail demonstration (Hopper Safety Audit regression)
With the bytemuck feature on (default), the following mis-use
patterns are all rejected at compile time. The audit’s Must-Fix #5
, “enforce field-level Pod proof at macro expansion time”, is
now mechanically enforced by bytemuck’s own Pod + Zeroable
bounds, so every zero-copy access path rejects them automatically.
bool is not Pod (the bit patterns 0x02..=0xFF don’t decode to
a valid bool):
let _ = account.segment_ref::<bool>(borrows, 16, 1);char is not Pod (valid Unicode scalar values form a sparse set):
let _ = account.segment_ref::<char>(borrows, 16, 4);A #[repr(C)] struct with implicit padding is not bytemuck-Pod
, bytemuck’s derive / Pod bound rejects the padding bytes because
they’d leak uninitialised data through bytes_of:
#[derive(Copy, Clone)]
#[repr(C)]
struct Padded {
a: u8,
// implicit 7 bytes of padding to align b
b: u64,
}
let _ = account.segment_ref::<Padded>(borrows, 16, 16);A type-level user mis-spelling unsafe impl Pod for Padded {}
without also satisfying bytemuck::Pod + Zeroable would fail at
the Pod supertrait bound. The compile-fail block above exercises
that path: no explicit impl Pod exists, and the access-path
generic requires it.
A well-formed primitive or wire type is accepted:
let _: Result<hopper_runtime::SegRef<'_, u64>, _> =
account.segment_ref::<u64>(borrows, 16, 8);§Trait identity across layers
When hopper-native-backend is active (the default), this trait is
a direct re-export of hopper_native::Pod. That keeps the entire
Hopper stack, substrate, runtime, core, macros, on a single Pod
trait: one unsafe impl Pod for MyStruct {} unlocks every Hopper
access API from the lowest-level AccountView::raw_mut up to
#[hopper::state]-generated accessors, across all crates, with no
orphan-rule gymnastics. When a non-native backend is selected
(legacy-pinocchio-compat, solana-program-backend), the trait is
defined locally with the same contract so user code compiles
unchanged.
Traits§
- Pod
- Marker for types that can be safely overlaid on raw account bytes.