Rust for Solana Basics
10 examples to get you started with Rust On-Chain - 7 basic and 3 intermediate.
Prerequisites
- Rust 1.91.1 with
cargoand the Solana CLI 3.0.10 toolchain (cargo build-sbf). - A program crate using
solana-program(native) or Anchor 0.32.1. - Local validator via Surfpool 0.12.0 or
solana-test-validatorfor integration tests.
Basic Examples
1. Program entrypoint
Every on-chain program exposes a single entrypoint! that receives accounts and instruction data.
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
pubkey::Pubkey,
};
entrypoint!(process_instruction);
pub fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
Ok(())
}- The runtime calls
process_instructionfor every instruction in a transaction. - Return
Ok(())on success; returnErr(ProgramError::...)to fail the transaction. - No
mainfunction - the BPF/SBF loader invokes your entrypoint directly.
Related: The Entrypoint & Dispatch - routing instructions
2. no_std environment
On-chain crates compile without the standard library.
#![no_std]
extern crate alloc;
use alloc::vec::Vec;#![no_std]disablesstd; useallocforVec,String, andBoxwhen needed.- No filesystem, threads, or networking - only Solana syscalls via
solana_program. - Keep dependencies minimal to reduce binary size and compute units (CUs).
Related: no_std & the Program Environment - available APIs
3. Borsh serialize account data
Most account layouts use Borsh for deterministic byte encoding.
use borsh::{BorshDeserialize, BorshSerialize};
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Counter {
pub count: u64,
}
let mut data = account.try_borrow_mut_data()?;
counter.serialize(&mut &mut data[..])?;- Borsh is the de facto format for instruction data and small account structs.
- Serialize into the account's byte slice; pre-allocate space at account creation.
- Always validate account length before deserializing.
Related: Borsh Serialization - full guide
4. Read a Pubkey from bytes
Pubkeys are 32-byte identifiers for accounts and programs.
use solana_program::pubkey::Pubkey;
let key = Pubkey::new_from_array(account.key.to_bytes());
if *account.key != expected_owner {
return Err(ProgramError::IncorrectProgramId);
}PubkeyimplementsCopyand compares in constant time - use it for owner checks.- Never trust client-supplied pubkeys without verifying against seeds or stored state.
- Program IDs are pubkeys derived from the program's keypair at deploy time.
5. Checked arithmetic
Overflow panics abort the transaction - use checked math.
let new_balance = old_balance
.checked_add(amount)
.ok_or(ProgramError::InvalidArgument)?;- Release builds on SBF do not trap on overflow the same way as host Rust - still use
checked_*. saturating_*is appropriate when capping values is acceptable.- Financial logic should never use unchecked
+,-, or*.
Related: Safe Arithmetic - overflow patterns
6. Return a program error
Fail fast with typed errors instead of panicking.
use solana_program::program_error::ProgramError;
if amount == 0 {
return Err(ProgramError::InvalidArgument);
}ProgramErrorcodes are compact and cheap compared to custom strings.- Custom error enums (via
thiserroror manual) map tou32codes for clients. - Never
unwrap()orexpect()in production program paths.
Related: Error Handling On-Chain - custom errors
7. Borrow account data
Account data is accessed through RefCell-style borrows.
let data = account.try_borrow_data()?;
if data.len() < Counter::LEN {
return Err(ProgramError::InvalidAccountData);
}try_borrow_datafails if the account is already mutably borrowed.- Drop borrows before CPIs that touch the same account.
- Validate
data.len()matches your struct size before parsing.
Intermediate Examples
8. Zero-copy account header
Large accounts can use bytemuck to reinterpret bytes without copying.
use bytemuck::{Pod, Zeroable};
#[repr(C)]
#[derive(Clone, Copy, Pod, Zeroable)]
pub struct Header {
pub discriminator: u64,
pub count: u64,
}
let header = bytemuck::from_bytes::<Header>(&data[..8]);Pod+Zeroablerequire#[repr(C)]and no padding surprises.- Zero-copy avoids heap allocation and saves CUs on hot paths.
- Pair with a Borsh tail for variable-length fields when needed.
Related: Zero-Copy with bytemuck - Pod rules
9. Log without blowing the CU budget
Use msg! sparingly; logs cost compute units.
solana_program::msg!("deposit: {} lamports", amount);- Logs appear in transaction metadata and simulation output.
- Format strings and many logs can dominate CU usage in tight loops.
- Prefer structured custom errors for client-facing failures.
Related: Logging with msg! - CU-aware logging
10. CU-conscious loop pattern
Bound iterations and avoid heap growth in hot paths.
const MAX_ITEMS: usize = 32;
for (i, item) in items.iter().take(MAX_ITEMS).enumerate() {
process_item(i, item)?;
}- Unbounded loops are a common audit finding and CU exhaustion vector.
- Pre-size vectors at account init instead of pushing in every instruction.
- Profile with
solana program show --programsand transaction simulation.
Related: Program Size & CU Discipline - optimization
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.