C.W.K.
Stream
Lesson 04 of 06 · published

Variables, Mutability & Shadowing

~11 min · foundations, variables, mutability, shadowing

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

Coming from Python or JavaScript, the first real Rust surprise is this: variables are immutable by default. let x = 5; binds a value you cannot change. Want to change it? You opt in with mut.

let and mut

let x = 5; is fixed. let mut x = 5; can be reassigned. This isn't bureaucracy — immutability is the default because most values shouldn't change, and the compiler can reason far more aggressively about things it knows are stable. You mark mutation exactly where it happens, and a reader sees it instantly.

const — compile-time constants

const MAX: u32 = 100_000; is a true constant: always immutable, must carry a type annotation, and is computed at compile time. Use const for values that are conceptually fixed forever (limits, conversion factors); use let for everything local.

Shadowing — the same name, a new value

You can declare a new variable with the same name as an old one. This isn't mutation — it's a brand-new binding that shadows the previous one, and it can even change type. It's the idiomatic way to transform a value through stages while keeping one good name.

Immutable by default is a feature, not a restriction. It pushes you to mark exactly what changes — which is exactly the information a future reader, and the compiler's optimizer, need most.

Type inference

Rust is statically typed but rarely makes you say so. let x = 5; infers i32. You annotate only when the compiler can't figure it out (like parsing a string into a number) or when you want to be explicit for clarity.

Code

let, mut, const, and shadowing·rust
const MAX_POINTS: u32 = 100_000; // compile-time constant, type required

fn main() {
    let x = 5;        // immutable
    // x = 6;         // error[E0384]: cannot assign twice to immutable variable

    let mut score = 0; // opt into mutation
    score += 10;       // fine

    // shadowing: a NEW binding, can even change type
    let spaces = "   ";          // &str
    let spaces = spaces.len();   // now usize — same name, new value
    println!("{score} {spaces} {MAX_POINTS}");
}

External links

Exercise

Start with a Celsius temperature as a string, "23.5". Using shadowing, rebind the same name temp three times: (1) the string, (2) the parsed number, (3) the Fahrenheit conversion. Use only the one name across all three stages.
Hint
Each stage is let temp = ...; shadowing the previous temp. The type flows &str -> f64 -> f64, which is exactly what shadowing is for.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Ttoriwarm

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.