Reading & Writing Account Data
Account data lives in a runtime-managed byte buffer. Programs borrow immutably or mutably, parse into types, mutate, and write back with Borsh or zero-copy updates.
Search across all documentation pages
Account data lives in a runtime-managed byte buffer. Programs borrow immutably or mutably, parse into types, mutate, and write back with Borsh or zero-copy updates.
let mut data = account.try_borrow_mut_data()?;
let state = State::deserialize(&mut &data[..])?;When to reach for this:
pub fn increment(account: &AccountInfo) -> ProgramResult {
if account.data_len() < State::LEN { return Err(ProgramError::InvalidAccountData); }
let mut data = account.try_borrow_mut_data()?;
let mut state = State::deserialize(&mut &data.as_ref()).map_err(|_| ProgramError::InvalidAccountData)?;
state.count = state.count.checked_add(1).ok_or(ProgramError::InvalidArgument)?;
state.serialize(&mut &mut data.as_mut()).map_err(|_| ProgramError::AccountDataTooSmall)?;
Ok(())
}What this demonstrates:
try_borrow_mut_data for writes.AccountDataTooSmall.Zero-copy allows field mutation without full serialize.
// Prefer &mut [u8] subslices for discriminator + body.| Alternative | Use When | Don't Use When |
|---|---|---|
| bytemuck field update | Hot path counters | Variable strings |
| Anchor Account::try_from | Auto owner/discriminator | Native |
| Read-only views | Pinocchio | Ecosystem |
Use try_ variants in programs - fail gracefully.
Account owner program only.
Yes if passed in transaction - validate owner.
Uninitialized - reject or init path.
Account allocated space.
Runtime serializes per account per tx.
Avoid - leaks user info.
Zero lamports after close - zero data first.
See realloc article.
More bytes need more lamports.
Use dedicated sysvar types.
LiteSVM with account fixtures.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.
Reviewed by Chris St. John·Last updated Jul 16, 2026