Skip to main content
Zero-copy deserialization allows programs to read and write account data directly from memory without copying or deserializing it. This is essential for handling large accounts efficiently on Solana.

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
Zero-Copy Benefits:
  • 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

Add bytemuck to enable zero-copy features:
Cargo.toml
The min_const_generics feature allows working with arrays of any size.

2. Define a Zero-Copy Account

Use the #[account(zero_copy)] attribute:
The attribute automatically implements required traits:
  • Copy - Allows bitwise copying
  • Clone - Enables cloning
  • bytemuck::Zeroable - Allows creation from zeroed bytes
  • bytemuck::Pod - “Plain Old Data” marker
  • #[repr(C)] - C-compatible memory layout

3. Use AccountLoader

Replace Account<'info, T> with AccountLoader<'info, T>:

Account Operations

Initialize (Small Accounts ≤ 10240 bytes)

For accounts up to 10,240 bytes, use the init 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 the zero constraint and create the account separately: Program:
Client (create account first):

Read Data

Use load() for read-only access:

Update Data

Use load_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 derive AnchorSerialize/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 be Copy types:

3. Using load_init vs load_mut

Use load_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:
Used by: Trading protocols, audit logs, messaging systems

Order Book Pattern

Efficient storage for trading pairs:
Used by: DEXs (Serum, Mango), NFT marketplaces

Ring Buffer Pattern

Circular buffer for fixed-size history:

Best Practices

Use std::mem::size_of to calculate exact sizes:
Comment size calculations for clarity:
Make code more readable:
Test with large accounts to verify compute budget:

Resources