Rust Syntax Practice
Practice Day 1 Rust with three focused exercises that force you to use variables, functions, control flow, loops, and basic input handling without guessing.
The fastest way to forget Day 1 is to read it once and move on.
The fastest way to keep it is to write small programs that force you to use the syntax deliberately.
That is what this page is for.
These exercises are not random puzzles. Each one is here to prove that you can use the Day 1 building blocks without relying on copy-paste luck.
Do these in order.
Each exercise adds one more layer of Rust pressure: variables and functions first, then control flow, then simple input handling.
What You Are Practicing
By the end of this page, you should be able to:
- create a Cargo project and edit
src/main.rs - choose sensible numeric types
- split logic into functions
- use
if,while, andforfor the right reasons - mutate state only when it actually changes
- parse simple terminal input without getting lost
If any of those still feels shaky, that is the point of the exercises.
Exercise 1: Transaction Fee Calculator
Start with the easiest useful task.
Write a program that takes a starting balance, a transfer amount, and a fixed network fee, then prints whether the transfer can happen and what the remaining balance would be.
Why this exercise matters
This forces you to practice:
- constants
- integer types
- function parameters and return values
ifbranches- basic arithmetic
What to build
Create a new Cargo project:
cargo new fee_calculator
cd fee_calculatorThen replace src/main.rs with this starter:
const NETWORK_FEE_LAMPORTS: u64 = 5_000;
fn main() {
let balance: u64 = 2_000_000;
let amount: u64 = 750_000;
if can_afford_transfer(balance, amount) {
let remaining = remaining_balance(balance, amount);
println!("Transfer approved. Remaining balance: {}", remaining);
} else {
println!("Transfer rejected. Not enough lamports.");
}
}
fn can_afford_transfer(balance: u64, amount: u64) -> bool {
// write this
false
}
fn remaining_balance(balance: u64, amount: u64) -> u64 {
// write this
0
}Your job
- implement
can_afford_transfer - implement
remaining_balance - run the program with
cargo run - change the numbers and predict the result before running again
What success looks like
You should be able to explain:
- why
u64is a better fit thani64here - why
NETWORK_FEE_LAMPORTSis aconst - why one function returns
booland the other returnsu64
If you cannot explain those choices, you are still memorizing syntax instead of understanding it.
Exercise 2: Confirmation Countdown
Now practice loops and mutation.
Write a program that simulates waiting for transaction confirmations.
Why this exercise matters
Rust beginners often understand variables in isolation but get sloppy once a value has to change repeatedly.
This exercise forces you to manage state carefully.
What to build
Use this starter code:
fn main() {
let mut confirmations_left = 5;
while confirmations_left > 0 {
println!("Waiting for confirmation {}...", confirmations_left);
confirmations_left -= 1;
}
println!("Transaction finalized.");
}After that works, extend it.
Add a second example that loops over a fixed list of transfer amounts:
fn main() {
let transfers = [100_000_u64, 200_000, 350_000];
for amount in transfers {
println!("Observed transfer: {} lamports", amount);
}
}Your job
- run the
whileversion and confirm you understand why it needsmut - rewrite the countdown so it starts from a different number
- add the
forexample below it - explain why
whileandforare solving different problems here
What success looks like
You should be able to say this in plain language:
whileis good when repetition depends on a changing conditionforis good when I already have a range or collection to iterate over
That distinction matters later when you start reading real Rust code.
Exercise 3: Simple Transfer Checker With Input
The first two exercises stayed fully hardcoded.
Now add one layer of interactivity.
Why this exercise matters
You need to see how Rust handles terminal input, strings, parsing, and failure cases.
This is still basic, but it is the first time your program depends on real user input instead of fixed values.
What to build
Create a program that:
- asks the user for a starting balance
- asks for a transfer amount
- parses both as
u64 - prints whether the transfer can happen after a fixed fee
Use this starter:
use std::io;
const NETWORK_FEE_LAMPORTS: u64 = 5_000;
fn main() {
println!("Enter your starting balance in lamports:");
let balance = read_u64();
println!("Enter the transfer amount in lamports:");
let amount = read_u64();
if balance >= amount + NETWORK_FEE_LAMPORTS {
println!("Transfer approved.");
println!(
"Remaining balance: {}",
balance - amount - NETWORK_FEE_LAMPORTS
);
} else {
println!("Transfer rejected. Not enough lamports.");
}
}
fn read_u64() -> u64 {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("failed to read input");
input
.trim()
.parse::<u64>()
.expect("please enter a valid unsigned integer")
}Read this code carefully
Two details matter here.
First, terminal input starts as a String, not as a number.
Second, .parse::<u64>() can fail, which is why the code uses .expect(...).
That is your first real example of Rust making you face the possibility of invalid input.
Your job
- run the program with valid input
- run it again with invalid input like
abc - observe the failure message
- change the code so the fee is shown separately in the output
- move the approval check into a helper function if you want extra practice
What success looks like
You should understand this sequence clearly:
- read text from stdin
- trim whitespace
- parse into a numeric type
- handle the possibility that parsing fails
That is a real programming workflow, not just Rust trivia.
How To Work Through These Exercises
Type the code yourself
Do not copy large blocks mindlessly.
Typing forces you to notice punctuation, types, and function signatures.
Change one thing at a time
After the program works, change one number, one type, or one branch condition and rerun it.
That is how you build intuition instead of only collecting finished code.
Read compiler errors fully
If Rust rejects the code, do not skip to random fixes.
Read the full message and ask what rule the compiler is enforcing.
Common Failure Modes
Using the wrong numeric type
If a value cannot be negative, defaulting to an unsigned integer like u64 is usually the cleaner choice.
That is why balances and lamports in these exercises use u64.
Forgetting that input is text first
read_line does not magically give you a number.
It gives you a string. Parsing is a separate step.
Reaching for mutation everywhere
Use mut only when the value actually changes.
If a value never changes, keep it immutable.
Summary
These exercises were designed to make Day 1 concrete.
If you finished them honestly, you have now used:
- constants
- immutable and mutable variables
- function signatures
- boolean logic
- loops
- arrays
- terminal input
- numeric parsing
That is enough practice to move into the next lesson without Day 1 being a blur.
The next step is ownership and borrowing.
That is where Rust stops feeling like just new syntax and starts feeling like a different way of thinking about memory.