Level 1 · Sprout

Variables & Mutability 📦

Programs need to remember things: scores, names, totals. Variables are how. And Rust has a strong opinion about them that will surprise you.

Variables are labeled boxes

Picture a box with a label on it. let creates the box, the label goes on the left of =, and what's inside goes on the right:

fn main() {
    let apples = 5;
    let name = "Rusty";
    println!("{name} has {apples} apples");
}

Inside the quotes of println!, curly braces { } are little windows: {name} means "show the value of name here."

The plot twist: boxes are sealed 🔒

In most languages, you can change a variable whenever you like. In Rust, this does not compile:

fn main() {
    let apples = 5;
    apples = 6; // ❌ nope!
}
error[E0384]: cannot assign twice to immutable variable `apples`
 --> src/main.rs:3:5
  |
2 |     let apples = 5;
  |         ------ first assignment to `apples`
3 |     apples = 6;
  |     ^^^^^^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable: `mut apples`

Rust variables are immutable by default: sealed boxes. Why? Because a huge share of bugs come from data changing when you didn't expect it. If nothing can change unless you explicitly allow it, whole categories of bugs vanish. Safety by default is the Rust theme, and this is your first taste of it.

Opening the lid: mut

When you genuinely want a value to change, say so out loud with mut:

fn main() {
    let mut score = 0;
    score = score + 10;  // ✅ allowed, we asked for a mutable box
    score += 5;          // shorthand for score = score + 5
    println!("score: {score}");
}

Now anyone reading your code (including you, at 2am) knows exactly which values can change and which are set in stone. It's documentation the compiler enforces.

Constants: carved in rock 🪨

For values that will never, ever change, use const. By convention the name is ALL_CAPS, and you must write the type (more on types next lesson):

const MAX_PLAYERS: u32 = 8;
const SPEED_OF_LIGHT_MS: u32 = 299_792_458; // underscores make big numbers readable

fn main() {
    println!("Up to {MAX_PLAYERS} players allowed.");
}

Shadowing: the box-swap trick 🎩

You can reuse a name with a fresh let. That's not mutation; it's a brand-new box wearing the old label:

fn main() {
    let message = "  hello  ";
    let message = message.trim();   // new box: spaces removed
    let message = message.len();    // new box: now it's a NUMBER (5)
    println!("{message}");
}

Notice the superpower: shadowing may even change the type. Text became a number. Rustaceans use this for step-by-step transformations, so you don't need names like message_trimmed_final_v2.

⚠️ Common stumbles
  • Reassigning without mut: read the error; it literally suggests the fix.
  • Adding mut to everything "just in case": resist! Default to immutable; add mut only when the compiler asks and it makes sense.
  • Declaring a variable you never use: Rust warns you (prefix with _ like _unused to hush it on purpose).
Exercise 1

Fix the crab counter

This program won't compile. Fix it two different ways: once with mut, once with shadowing.

fn main() {
    let crabs = 10;
    crabs = crabs + 5;
    println!("{crabs} crabs on the beach");
}
Reveal solution
// Way 1: mutable box
fn main() {
    let mut crabs = 10;
    crabs = crabs + 5;
    println!("{crabs} crabs on the beach");
}

// Way 2: shadowing (a new box each time)
fn main() {
    let crabs = 10;
    let crabs = crabs + 5;
    println!("{crabs} crabs on the beach");
}
Exercise 2

Level-up counter

Create a mutable level starting at 1. Add 1 to it three times, printing "Level up! Now level N" after each change.

Reveal solution
fn main() {
    let mut level = 1;
    level += 1;
    println!("Level up! Now level {level}");
    level += 1;
    println!("Level up! Now level {level}");
    level += 1;
    println!("Level up! Now level {level}");
}

Typing the same thing three times felt silly, right? Good instinct: loops (Lesson 5) will fix that.

Exercise 3

Predict the output

Without running it, what does this print?

fn main() {
    let x = 2;
    let x = x * x;
    let x = x + 1;
    println!("{x}");
}
Reveal answer

5. Each let makes a new x from the old one: 2 → 4 → 5. Shadowing in action.