Why Use Zero-Copy?
Traditional Anchor accounts (Account<T>) copy data from the account into a heap-allocated struct during deserialization. This has significant limitations:
Limitations:
- Stack Limit: 4KB maximum
- Heap Limit: 32KB maximum
- Compute Cost: Deserialization consumes compute units proportional to data size
- Memory Overhead: Data is duplicated in memory
- Direct Access: Casts raw account bytes to struct type (no copying)
- Larger Accounts: Supports accounts up to 10MB (10,485,760 bytes)
- Lower Compute: ~90% reduction in CU usage for large accounts
- In-Place Updates: Modifies account data directly
Performance Comparison
When to Use Zero-Copy
Use Zero-Copy For
- Accounts larger than 1KB
- Arrays with many elements (orderbooks, event queues)
- High-frequency read/write operations
- Compute-sensitive programs
- Fixed-size data structures
Use Regular Account<T> For
- Small accounts (< 1KB)
- Dynamic data structures (Vec, String, HashMap)
- Frequently changing schemas
- Simple state that doesn’t need optimization
Basic Usage
1. Add Bytemuck Dependency
Addbytemuck to enable zero-copy features:
Cargo.toml
min_const_generics feature allows working with arrays of any size.
2. Define a Zero-Copy Account
Use the#[account(zero_copy)] attribute:
Copy- Allows bitwise copyingClone- Enables cloningbytemuck::Zeroable- Allows creation from zeroed bytesbytemuck::Pod- “Plain Old Data” marker#[repr(C)]- C-compatible memory layout
3. Use AccountLoader
ReplaceAccount<'info, T> with AccountLoader<'info, T>:
Account Operations
Initialize (Small Accounts ≤ 10240 bytes)
For accounts up to 10,240 bytes, use theinit constraint:
The
init constraint is limited to 10,240 bytes due to CPI limitations when calling the System Program.Initialize (Large Accounts > 10240 bytes)
For accounts larger than 10,240 bytes, use thezero constraint and create the account separately:
Program:
Read Data
Useload() for read-only access:
Update Data
Useload_mut() for mutable access:
Advanced Patterns
Nested Zero-Copy Types
Define reusable zero-copy types with#[zero_copy] (without account):
Accessor Methods for Byte Arrays
Since zero-copy uses#[repr(packed)], field references are unsafe. Use #[accessor] for safe getters/setters:
Zero-Copy with PDAs
Zero-copy accounts work seamlessly with PDAs:Separate Types for RPC Parameters
Zero-copy types cannot deriveAnchorSerialize/AnchorDeserialize. Use separate types for instruction parameters:
Common Pitfalls
1. Forgetting the Account Discriminator
Always add 8 bytes for the account discriminator:2. Using Dynamic Types
Zero-copy requires all fields to beCopy types:
3. Using load_init vs load_mut
Useload_init() only for first-time initialization:
4. Not Validating Array Indices
Always validate array indices to prevent panics:Real-World Use Cases
Event Queue Pattern
Store large sequences of events efficiently:Order Book Pattern
Efficient storage for trading pairs:Ring Buffer Pattern
Circular buffer for fixed-size history:Best Practices
Calculate Space Requirements Accurately
Calculate Space Requirements Accurately
Use
std::mem::size_of to calculate exact sizes:Document Size Constraints
Document Size Constraints
Comment size calculations for clarity:
Use Type Aliases for Large Arrays
Use Type Aliases for Large Arrays
Make code more readable:
Test with Maximum Sizes
Test with Maximum Sizes
Test with large accounts to verify compute budget: