Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

FeatureAdds
fixedMappings for signed and unsigned fixed 1.30.0 values
floatsPodF32/PodF64 and mappings for native f32/f64
solana-addressA mapping for solana_address::Address
solana-program-errorConversion from PinaPodError to ProgramError
wincodeCanonical 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

TraitContract
PinaPodMarks a schema generated by the derive
PinaPodFixedProvides fixed-size read, validation, and initialization operations
PinaPodCompactDefines compact storage bounds, allocation granularity, and validation
ZcValidateChecks whether initialized bytes contain a semantic value
ZcElemStates that a stored type is alignment one and safe for packed access
ZcFieldMaps 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:

VariantMeaning
BufferTooSmallThe supplied slice cannot contain the required header, value, or active tail
OverflowA requested write exceeds a field capacity or checked arithmetic fails
InvalidBoolA stored boolean byte is not zero or one
InvalidTagA stored option tag is not zero or one
InvalidDiscriminantA stored enum value has no declared variant
InvalidLengthA stored length exceeds capacity or violates the read contract
InvalidUtf8Active 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.

TypeStored sizeMeaning
PodU16 through PodU1282 through 16 bytesUnsigned, little-endian integer
PodI16 through PodI1282 through 16 bytesSigned, little-endian integer
PodBool1 byteBoolean with a 0 or 1 byte
PodF324 bytesIEEE-754 binary32 stored as its bit pattern
PodF648 bytesIEEE-754 binary64 stored as its bit pattern
PodOption<T, PFX>PFX + size_of::<T>()Optional fixed representation
PodString<N, PFX>PFX + NUTF-8 string with at most N bytes
PodVec<T, N, PFX>PFX + N * mapped element sizeVector 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.