Rust Setup and Core Syntax
Install the Rust toolchain, create your first Cargo project, and learn the variables, functions, types, and control flow that later Solana lessons depend on.
A lot of new Solana learners think Rust becomes hard only when ownership starts.
That is not true.
Many people get blocked earlier because their toolchain is half-set up, they do not know what cargo is doing, and basic Rust syntax still feels unfamiliar.
This lesson fixes that.
The goal is simple: get a working Rust environment, write code that compiles, and understand the small language rules that show up in every later Solana lesson.
Treat the compiler as part of the lesson.
If Rust rejects your code, that is not friction to ignore. It is the language telling you what rule you just violated.
The Mental Model For This Lesson
You do not need to learn all of Rust today.
You need to learn the workflow:
- write a small program
- compile it with
cargo - read what the compiler says
- fix the code until the program does what you expect
That loop is the foundation for everything else.
By the end of this lesson, you should be comfortable with:
- the Rust toolchain:
rustup,rustc, andcargo - creating and running a project
- variables and mutability
- basic scalar types
- functions and return values
if,loop,while, andfor
Install The Rust Toolchain
Rust development usually starts with three tools:
rustup: installs and manages Rust toolchainsrustc: the Rust compilercargo: the project manager and build tool
You will use cargo most of the time. rustc is the compiler under the hood.
Install Rust
On macOS and Linux:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shOn Windows, install rustup-init.exe from rustup.rs.
Verify the installation
Open a new terminal and run:
rustc --version
cargo --version
rustup showIf these commands work, your toolchain is installed correctly.
If they fail, fix that now. Do not move deeper into Rust with a broken environment.
Update to the latest stable toolchain
rustup update
rustup default stableThis makes sure your local setup matches the stable Rust ecosystem most tutorials assume.
Set Up Your Editor
Use VS Code with the official rust-analyzer extension.
That gives you the minimum useful developer experience:
- inline errors
- type hints
- jump to definitions
- quick fixes
You can add extras later. rust-analyzer is the only extension that really matters right now.
Create Your First Cargo Project
Do not start with a random .rs file.
Start with Cargo.
Cargo gives you the correct project structure, handles builds, and will later manage dependencies for real Solana projects.
cargo new day1_rust
cd day1_rust
cargo runWhen cargo run succeeds, three useful things happened:
- Cargo created a project directory
- Rust compiled your program into a binary
- Cargo ran that binary for you
You should see output similar to:
Compiling day1_rust v0.1.0
Finished dev profile
Running `target/debug/day1_rust`
Hello, world!That output matters.
It proves your local Rust workflow works end to end.
Read The Smallest Useful Rust Program
Open src/main.rs. You will see this:
fn main() {
println!("Hello, world!");
}This tiny file already teaches you several Rust rules.
fn main()declares the program entry point- braces
{}define the function body println!is a macro, which is why it ends with!- the line ends with
;, so it is a statement
At this stage, you do not need macro theory. You just need to recognize that println! writes text to the terminal.
Build One Small Program You Can Reason About
Instead of throwing isolated syntax examples at you, we will build one small Rust program.
The program will simulate a wallet balance check.
That keeps the logic simple while staying close to Solana-style thinking.
Replace src/main.rs with this:
const NETWORK_FEE_LAMPORTS: u64 = 5_000;
fn main() {
// `u64` is common for balances because balances cannot be negative.
let starting_balance: u64 = 2_000_000;
let transfer_amount: u64 = 750_000;
println!("Starting balance: {} lamports", starting_balance);
println!("Transfer amount: {} lamports", transfer_amount);
let final_balance = calculate_remaining_balance(starting_balance, transfer_amount);
println!("Final balance: {} lamports", final_balance);
}
fn calculate_remaining_balance(balance: u64, amount: u64) -> u64 {
balance - amount - NETWORK_FEE_LAMPORTS
}Run it:
cargo runWhat this teaches
constcreates a compile-time constantu64is an unsigned 64-bit integerletcreates an immutable variablefn ... -> u64means the function returns au64- the last line of a function can return a value without using
return
That last rule matters a lot in Rust.
The final line in calculate_remaining_balance is an expression, not a statement.
If you add a semicolon there, the function stops returning the number you expect.
Variables Are Immutable Unless You Opt In
This is one of the first Rust rules that surprises JavaScript learners.
In Rust, variables do not change unless you say they can change.
fn main() {
let balance = 100;
// balance = 200; // this does not compile
}If a value really needs to change, use mut.
fn main() {
let mut balance = 100;
balance = 200;
println!("{}", balance);
}This is not just syntax preference.
Rust makes mutation explicit because hidden state changes are a common source of bugs.
The Basic Types You Will Use First
You do not need the whole type system today.
You need the small set that shows up constantly.
fn main() {
let balance: u64 = 2_000_000;
let retry_count: u8 = 3;
let price: f64 = 1.25;
let is_confirmed: bool = true;
let grade: char = 'A';
println!("{} {} {} {} {}", balance, retry_count, price, is_confirmed, grade);
}Use this mental shortcut:
- integers like
u64for counts and balances f64for decimal valuesboolfor true or falsecharfor a single Unicode character
Rust can often infer types for you, but explicit types are useful when you want the code to be obvious.
Functions Are Where Rust Starts To Feel Structured
A function is just named behavior.
Here is the same wallet example, expanded slightly:
const NETWORK_FEE_LAMPORTS: u64 = 5_000;
fn main() {
let balance = 2_000_000;
let amount = 750_000;
if can_afford_transfer(balance, amount) {
let final_balance = calculate_remaining_balance(balance, amount);
println!("Transfer is valid. Final balance: {}", final_balance);
} else {
println!("Transfer would fail: not enough lamports.");
}
}
fn can_afford_transfer(balance: u64, amount: u64) -> bool {
balance >= amount + NETWORK_FEE_LAMPORTS
}
fn calculate_remaining_balance(balance: u64, amount: u64) -> u64 {
balance - amount - NETWORK_FEE_LAMPORTS
}Two points matter here.
First, function parameters need explicit types.
Second, Rust makes return types explicit too. That is what -> bool and -> u64 mean.
That strictness helps later when Solana programs start passing around accounts, lamports, and custom structs. The compiler forces you to be precise.
if Is Not Just Control Flow
In Rust, if can decide execution, but it can also produce a value.
That makes it more powerful than many beginners expect.
fn main() {
let priority_fee = true;
let extra_fee = if priority_fee { 10_000 } else { 0 };
println!("Extra fee: {}", extra_fee);
}Both branches must return the same kind of value.
That rule prevents messy code where one branch produces a number and the other produces something unrelated.
Learn The Three Loop Forms Early
Rust gives you three common ways to repeat work.
loop
Use loop when you want a manual exit with break.
fn main() {
let mut attempts = 0;
let final_attempt = loop {
attempts += 1;
if attempts == 3 {
break attempts;
}
};
println!("Stopped after {} attempts", final_attempt);
}while
Use while when repetition depends on a condition.
fn main() {
let mut confirmations_left = 3;
while confirmations_left > 0 {
println!("Waiting... {}", confirmations_left);
confirmations_left -= 1;
}
}for
Use for when iterating over a range or collection.
fn main() {
let transfers = [100_000, 250_000, 400_000];
for amount in transfers {
println!("Observed transfer: {}", amount);
}
}For beginner Rust, for is usually the cleanest loop to reach for first.
Put The Pieces Together
Now build one complete program that uses constants, variables, functions, if, and for together.
Replace src/main.rs with this:
const NETWORK_FEE_LAMPORTS: u64 = 5_000;
fn main() {
let starting_balance: u64 = 2_000_000;
let outgoing_transfers: [u64; 3] = [150_000, 300_000, 900_000];
let mut current_balance = starting_balance;
println!("Starting balance: {} lamports", current_balance);
for amount in outgoing_transfers {
if can_afford_transfer(current_balance, amount) {
current_balance = calculate_remaining_balance(current_balance, amount);
println!("Sent {} lamports", amount);
println!("New balance: {} lamports", current_balance);
} else {
println!("Skipped {} lamports: insufficient balance", amount);
}
}
println!("Finished with {} lamports", current_balance);
}
fn can_afford_transfer(balance: u64, amount: u64) -> bool {
balance >= amount + NETWORK_FEE_LAMPORTS
}
fn calculate_remaining_balance(balance: u64, amount: u64) -> u64 {
balance - amount - NETWORK_FEE_LAMPORTS
}Run it, then change the numbers yourself.
Try these edits:
- make one transfer too large and watch the
elsebranch run - change the starting balance and predict the output before running
- add a fourth transfer to the array and confirm the loop handles it
That is the right way to learn basic Rust syntax: change the program and observe what breaks or what changes.
Common Mistakes
Forgetting mut
If you try to reassign an immutable variable, Rust will stop you.
That error is correct. Add mut only when the value truly changes.
Accidentally returning () instead of a value
This fails:
fn add(a: i32, b: i32) -> i32 {
a + b;
}The semicolon turns the expression into a statement.
This works:
fn add(a: i32, b: i32) -> i32 {
a + b
}Mixing incompatible numeric types
Rust does not silently smooth over type mismatches for you.
If one value is u8 and another is u64, be explicit about conversions.
Summary
This lesson was about getting the Rust workflow under control.
You now have the basics that later Solana Rust lessons rely on:
- a working Rust toolchain
- a usable Cargo workflow
- immutable and mutable variables
- core scalar types
- functions with explicit return types
if,loop,while, andfor
That is enough to start reading and writing small Rust programs without guessing what the syntax is doing.
The next lesson goes deeper into the rule that makes Rust feel fundamentally different: ownership and borrowing.
Before you move on, do the practice page while this syntax is still fresh.