Traits, errors, and features
Most schema code imports only PinaPod. The lower-level traits exist for generic framework code and audited representation extensions.
Feature flags
| Feature | Adds |
|---|---|
fixed | Mappings for signed and unsigned fixed 1.30.0 values |
floats | PodF32/PodF64 and mappings for native f32/f64 |
solana-address | A mapping for solana_address::Address |
solana-program-error | Conversion from PinaPodError to ProgramError |
wincode | Canonical SchemaRead and SchemaWrite implementations |
No feature is enabled by default, so the core crate stays no_std and dependency-free.
Enable only what a program reads from or writes to the wire.
Traits
| Trait | Contract |
|---|---|
PinaPod | Marks a schema generated by the derive |
PinaPodFixed | Provides fixed-size read, validation, and initialization operations |
PinaPodCompact | Defines compact storage bounds, allocation granularity, and validation |
ZcValidate | Checks whether initialized bytes contain a semantic value |
ZcElem | States that a stored type is alignment one and safe for packed access |
ZcField | Maps a native schema field to its stored pod representation |
PinaPodFixed, PinaPodCompact, ZcElem, and ZcField are unsafe to implement. Use the derive unless you need an audited representation that the crate does not provide.
Errors
Safe operations return PinaPodError:
| Variant | Meaning |
|---|---|
BufferTooSmall | The supplied slice cannot contain the required header, value, or active tail |
Overflow | A requested write exceeds a field capacity or checked arithmetic fails |
InvalidBool | A stored boolean byte is not zero or one |
InvalidTag | A stored option tag is not zero or one |
InvalidDiscriminant | A stored enum value has no declared variant |
InvalidLength | A stored length exceeds capacity or violates the read contract |
InvalidUtf8 | Active string bytes are not UTF-8 |
The solana-program-error feature maps a small buffer to ProgramError::AccountDataTooSmall.
It maps every invalid representation and every write overflow to ProgramError::InvalidAccountData.
Pod types
Every schema field maps to one of these alignment-one representations. PFX is a prefix width in bytes.
| Type | Stored size | Meaning |
|---|---|---|
PodU16 through PodU128 | 2 through 16 bytes | Unsigned, little-endian integer |
PodI16 through PodI128 | 2 through 16 bytes | Signed, little-endian integer |
PodBool | 1 byte | Boolean with a 0 or 1 byte |
PodF32 | 4 bytes | IEEE-754 binary32 stored as its bit pattern |
PodF64 | 8 bytes | IEEE-754 binary64 stored as its bit pattern |
PodOption<T, PFX> | PFX + size_of::<T>() | Optional fixed representation |
PodString<N, PFX> | PFX + N | UTF-8 string with at most N bytes |
PodVec<T, N, PFX> | PFX + N * mapped element size | Vector with at most N mapped pod elements |
A schema field declared as a native type maps to its pod through ZcField, so the derive accepts spellings such as u64, bool, f32, and [u8; 32] directly. Pod types that appear in a schema without a mapping are stored as-is.
Numeric pods
Numeric pod types store little-endian bytes and have alignment one. Use get and set to cross the unaligned representation boundary.
#![allow(unused)]
fn main() {
use pinapod::pod::PodU64;
let mut amount = PodU64::from(100_u64);
amount.set(125);
assert_eq!(amount.get(), 125);
assert_eq!(amount.checked_sub(25), Some(PodU64::from(100)));
}
Numeric pods do not implement arithmetic, remainder, bitwise, shift, assignment, or negation operators. Choose the overflow contract at the call site with the retained checked_*, wrapping_*, and saturating_* methods. Signed pods also provide checked_neg and wrapping_neg. For another operation, decode with get, apply the native operation, and convert the result back.
Pod-to-pod comparisons and pod-left comparisons with native integers remain available. Native-left reverse PartialEq and PartialOrd implementations do not.
How the derive recognizes container types
The derive classifies a field as a dynamic string, vector, or option by its spelling: an unqualified String, Vec, or Option, a path through the resolved pinapod dependency (including a pinapod::pod:: prefix), or a pina:: re-export. Every other path is treated as a fixed inline type that must provide its own ZcField mapping.
This has one consequence worth knowing: a module of yours that is literally named pinapod (or pina) containing its own String/Vec/Option types is classified as PinaPod’s dynamic containers, because the derive matches the path segments rather than the resolved crate. The result stays memory-safe — the field is still validated before reference formation — but its wire meaning changes silently. Avoid module names that shadow the dependency, or spell such fields through an unambiguous path. Type names alone never grant a built-in representation: a caller-local struct i8(bool) lookalike still fails to compile as a schema field.
Errors and prefix widths
PinaPodError implements core::error::Error and is non_exhaustive: match with a wildcard arm so new validation variants can arrive in minor releases. All three container families accept prefix widths of 1, 2, 4, or 8 bytes; PodOption reports its raw decoded tag as u64 so an eight-byte tag can never truncate into a valid value.
Compact storage bounds
PinaPodCompact defines MIN_SIZE, MAX_SIZE, and TAIL_ALIGNMENT. validate_storage_len rejects an allocation outside the inclusive size bounds or whose growth beyond MIN_SIZE is not a multiple of TAIL_ALIGNMENT. Generated compact readers perform this check before they validate active fields and tails.
A direct compact derive exposes the same values as inherent Type::MIN_SIZE, Type::MAX_SIZE, and Type::TAIL_ALIGNMENT constants. Framework code can use the trait constants when it suppresses generated inherent helpers.
Fixed-point values
Enable fixed to map every signed and unsigned fixed width to its integer pod. PinaPod pins fixed 1.30.0 because later versions require a newer compiler than PinaPod’s Rust 1.89 baseline.
[dependencies]
fixed = { version = "=1.30.0", default-features = false }
pinapod = { version = "0.2", features = ["fixed"] }
#![allow(unused)]
fn main() {
use fixed::types::I16F16;
use pinapod::PinaPod;
#[derive(PinaPod)]
struct Price {
mark: I16F16,
}
let mut data = [0_u8; Price::SIZE];
Price::initialize(&mut data, |price| {
price.mark = I16F16::from_num(10.25).to_bits().into();
Ok(())
})?;
let price = Price::read_exact(&data)?;
let mark = I16F16::from_bits(price.mark.get());
assert_eq!(mark, I16F16::from_num(10.25));
Ok::<(), pinapod::PinaPodError>(())
}
Fixed-point storage contains only the raw little-endian bits. The scale comes from the Rust field type, not from extra wire metadata.
IEEE-754 float values
Enable floats to add PodF32 and PodF64 and map the native f32 and f64 primitives to them. A schema field declared as f32 or f64 is accepted directly, and the generated accessor decodes back to the native float.
[dependencies]
pinapod = { version = "0.3", features = ["floats"] }
#![allow(unused)]
fn main() {
use pinapod::PinaPod;
#[derive(PinaPod)]
struct Reading {
temperature: f32,
depth: f64,
}
let mut data = [0_u8; Reading::SIZE];
Reading::initialize(&mut data, |reading| {
reading.temperature.set(-12.5);
reading.depth.set(3.125);
Ok(())
})?;
let reading = Reading::read_exact(&data)?;
assert_eq!(reading.temperature(), -12.5);
assert_eq!(reading.depth(), 3.125);
Ok::<(), pinapod::PinaPodError>(())
}
get and set convert to and from the native float; to_bits and set_bits expose the raw pattern.
Storage is the complete IEEE-754 bit pattern, little-endian.
Every bit pattern is a valid stored value, so validation never rejects a NaN, an infinity, or the sign of zero, and an all-zero field decodes as +0.0.
Equality compares stored bit patterns rather than decoded floats.
That keeps Eq sound in the presence of NaN payloads and preserves the distinction between +0.0 and -0.0. The pods deliberately implement no PartialOrd or Ord, because bitwise equality and float ordering cannot both hold: an ordering would have to rank NaN payloads and separate +0.0 from -0.0. Decode with get and compare the natives when an ordering is needed.
PodF32 and PodF64 are byte containers, not arithmetic types: they provide no operators, so multi-step float math belongs at the call site on the decoded natives.
For fractional values that carry economic meaning, prefer fixed-point storage. Fixed-point arithmetic is exact integer math, while native float operations round at every step and have no hardware accelerated unit on the SBF target.
Other optional integrations
The solana-address feature maps solana_address::Address to its alignment-one 32-byte representation.
The wincode feature implements canonical SchemaRead and SchemaWrite for pod types. Nested fixed containers compose. Writers zero inactive capacity, and the types do not claim direct-borrow ZeroCopy support.