-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Zero-copy account deserialization by default #4390
Description
Anchor V2 should use zero-copy (in-place) deserialization as the default account access pattern, replacing Borsh deserialize + heap copy. Borsh-style copying should remain available as an explicit opt-in for backwards compatibility.
Today Anchor has two worlds:
Account<T> (default) |
AccountLoader<T> (opt-in) |
|
|---|---|---|
| Deserialization | Full Borsh copy onto heap | Zero-copy via bytemuck |
| Ergonomics | .field access directly |
.load() / .load_mut() required |
| CU cost | High — allocates + copies | Low — reads from input buffer |
| Alignment | No restrictions | Requires #[repr(C)], Pod, padding |
The default path (Account<T>) is the slow path. Every account is fully deserialized on entry and re-serialized on exit, even if the instruction only reads one field. This costs significant CUs and stack space — the #1 performance complaint from Anchor developers.
As #3742 discussion feedback put it: "the default serialization should probably behave more like zero-copy but with better UX."
Problem with today's zero-copy
The current AccountLoader + #[account(zero_copy)] approach works, but the UX is painful:
- Byte alignment burden. Users must manually ensure
#[repr(C)]andPodcompliance — nobool, noenum, noString, manual padding bytes. - Two codegen paths.
#[account]and#[account(zero_copy)]produce completely different code, making it hard to switch between them. - Ref-counting overhead.
AccountLoaderusesRefCellinternally, adding runtime borrow-checking that Pinocchio eliminates. - IDL divergence. The IDL must encode
serialization: "bytemuck"separately, and not all client generators handle it.
Proposed design for V2
Make zero-copy the default, with ergonomic access and no alignment footguns.
-
Unified
Account<T>(Collapse all account wrappers intoAccount<T>#4273) uses zero-copy access by default. The serializer is determined by the#[account]attribute or the plugin API (Design the custom de/serializer plugin API #4276), not by which wrapper type you pick. -
Automatic padding and alignment. The
#[account]derive macro should insert padding fields automatically to satisfy alignment requirements, similar to how C compilers handle struct layout. Users write natural Rust structs; the macro handlesrepr(C)and padding. -
Derefergonomics.Account<T>should implementDeref<Target = T>so field access is.field— no.load()ceremony. Mutable access viaDerefMutor an explicit.as_mut()that the framework can track for dirty-checking on exit. -
Borsh as opt-in. For types that genuinely need dynamic-length serialization (
Vec<T>,String,Option<T>), users annotate with#[account(serialization = "borsh")]to opt into copy-based deserialization. This is the escape hatch, not the default. -
Lazy deserialization. For instructions that don't touch every account, the framework should support lazy field access — only read the bytes you touch. This pairs naturally with the Pinocchio entry point (Migrate from Solana program model to Pinocchio runtime #4068).
Backwards compatibility
- Programs compiled with Anchor V1 that use
Account<T>+ Borsh continue to work unchanged — their on-chain data format doesn't change. - V2 programs using the new zero-copy default produce a different on-chain layout (aligned,
repr(C)). The IDL captures this via theserializationandreprfields. - The
anchor::legacymodule (Provide anchor::legacy module for V1 account types #4277) re-exports V1-style BorshAccount<T>for migration. - A migration guide should cover the layout differences and how to handle live account upgrades (see
Migration<From, To>type from PR AddMigration<'info, From, To>account type #4060).
Related issues
- Collapse all account wrappers into
Account<T>#4273 — Collapse all account wrappers intoAccount<T> - Design the custom de/serializer plugin API #4276 — Design the custom de/serializer plugin API
- Unify the three #[account] codegen paths #4283 — Unify the three
#[account]codegen paths - Evaluate Wincode as the default serialization format #4286 — Evaluate Wincode as default serialization format
- Provide anchor::legacy module for V1 account types #4277 —
anchor::legacymodule for V1 types - Migrate from Solana program model to Pinocchio runtime #4068 — Pinocchio runtime migration
- What do you want to see in Anchor V2? #3742 — V2 community discussion (multiple requests for this)
Metadata
Metadata
Assignees
Labels
Type
Projects
Status