learn.sol
Week 2 • Day 1 Challenges

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, and for for 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
  • if branches
  • basic arithmetic

What to build

Create a new Cargo project:

cargo new fee_calculator
cd fee_calculator

Then replace src/main.rs with this starter:

src/main.rs
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

  1. implement can_afford_transfer
  2. implement remaining_balance
  3. run the program with cargo run
  4. change the numbers and predict the result before running again

What success looks like

You should be able to explain:

  • why u64 is a better fit than i64 here
  • why NETWORK_FEE_LAMPORTS is a const
  • why one function returns bool and the other returns u64

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:

src/main.rs
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

  1. run the while version and confirm you understand why it needs mut
  2. rewrite the countdown so it starts from a different number
  3. add the for example below it
  4. explain why while and for are solving different problems here

What success looks like

You should be able to say this in plain language:

  • while is good when repetition depends on a changing condition
  • for is 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:

src/main.rs
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

  1. run the program with valid input
  2. run it again with invalid input like abc
  3. observe the failure message
  4. change the code so the fee is shown separately in the output
  5. 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.

Solana Assistant

AI-powered documentation helper

Welcome to Solana Assistant

Ask specific questions about Solana development:

Ask specific questions for better results400px
    Rust Syntax Practice | learn.sol