Rust primer (for Solana, in 10 minutes)
⏱ 10 min · 🎯 you'll understand: just enough Rust to read and edit Anchor programs.
You don't need to be a Rust expert to use sunscreen. You need to read code generated by scaffold and tweak it. This primer gives you exactly that.
The 30-second mental model
Rust is statically typed, has no garbage collector, and tracks who owns each value at compile time. In Anchor programs, the heavy machinery is hidden behind macros — #[program], #[derive(Accounts)], #[account]. You mostly write straight-line code inside instruction handlers.
Ownership (in 1 paragraph)
Every value has one owner. When the owner goes out of scope, the value is dropped (memory freed). You can lend a value out with &value (read-only) or &mut value (read-write). At any moment, you can have either many read-only borrows or one mutable borrow. The compiler enforces this — get used to error messages naming "borrow checker". For Anchor programs, this almost never bites you, because the framework manages account data.
Structs and impls
#![allow(unused)] fn main() { pub struct Counter { pub count: u64, } impl Counter { pub fn increment(&mut self) { self.count += 1; } } }
A struct holds fields. An impl block defines methods. &mut self means "this method can modify the struct".
Anchor's three macros
This is 90% of what you'll see in scaffolded code.
#[program]
#![allow(unused)] fn main() { #[program] pub mod my_app { use super::*; pub fn greet(ctx: Context<Greet>, name: String) -> Result<()> { msg!("Hello, {}!", name); Ok(()) } } }
The #[program] macro turns each pub fn into a callable instruction. Context<Greet> carries the accounts the instruction needs (declared next). Result<()> is Rust's standard error type — Ok(()) means success with no return value.
#[derive(Accounts)]
#![allow(unused)] fn main() { #[derive(Accounts)] pub struct Greet<'info> { #[account(mut)] pub user: Signer<'info>, pub system_program: Program<'info, System>, } }
This declares which accounts the instruction takes. mut = will be modified. Signer = must sign the transaction. The 'info is a lifetime — think of it as "these accounts live for the duration of this call". You'll rarely touch lifetimes manually.
#[account]
#![allow(unused)] fn main() { #[account] pub struct Counter { pub count: u64, pub authority: Pubkey, } }
#[account] marks a struct as on-chain storage. Anchor handles serialization/deserialization, plus a discriminator prefix.
Common types you'll see
| Type | What it is |
|---|---|
u8 .. u64, i8 .. i64 | unsigned/signed integers |
bool | true / false |
String | heap-allocated UTF-8 string |
Vec<T> | heap-allocated growable array of T |
Pubkey | 32-byte Solana public key |
Option<T> | Some(T) or None |
Result<T, E> | Ok(T) or Err(E) |
Error handling
In Anchor handlers you'll see:
#![allow(unused)] fn main() { require!(amount > 0, MyError::AmountZero); some_account.field = value; Ok(()) }
require! is a macro: if the condition is false, it returns Err(MyError::AmountZero). Otherwise execution continues. Ok(()) returns success.
The ? operator
#![allow(unused)] fn main() { let data = account.try_borrow_data()?; }
The ? says: if the call returned Err, propagate it up; if Ok, unwrap the value and continue. It's a shortcut for match.
What you can safely ignore (for now)
- Traits and generics beyond what Anchor uses.
- Async/await (Anchor programs are synchronous).
- Smart pointers (
Box,Rc,Arc) — Anchor doesn't need them in handlers. - Macros internals — just use them.
Going deeper
When you're ready: https://doc.rust-lang.org/book/ is the canonical, well-paced book. The chapters that matter for Anchor:
- Ch. 4: ownership + borrowing.
- Ch. 5–6: structs and enums.
- Ch. 9: error handling.
- Ch. 13: closures and iterators (useful for client code).
Next
- Solana primer — accounts, programs, PDAs, transactions.
- Your first workspace — apply this to read what sunscreen generates.