RustArena

Shadowing in Rust: reusing a variable name with let

What shadowing is in Rust, how let x = x + 1 differs from mut, changing a value's type under the same name, and shadowing in inner blocks.

Shadowing means declaring a new variable with the same name as an existing one. The new variable hides (shadows) the old one from that point on:

fn main() {
    let level = 1;
    let level = level + 1;
    let level = level * 10;
    println!("level {level}");
}
level 20

Each let creates a brand new variable. Nothing is modified, so level doesn't need mut.

Shadowing vs mut

Two differences matter:

  1. The type can change. A shadowing let can give the name a value of a different type. This is the idiomatic way to convert input: read a String, then shadow it with the parsed number. You don't need two names like age_str and age.
  2. The result is still immutable. After let total = total + tax; the new total can't be changed by accident later.

With mut, the type is fixed forever, so this fails:

fn main() {
    let mut spaces = "   ";
    spaces = spaces.len();
    println!("{spaces}");
}
error[E0308]: mismatched types
 --> src/main.rs:3:14
  |
2 |     let mut spaces = "   ";
  |                      ----- expected due to this value
3 |     spaces = spaces.len();
  |              ^^^^^^^^^^^^ expected `&str`, found `usize`

Writing let spaces = spaces.len(); instead compiles: it's a new variable of type usize.

Shadowing in an inner block

A shadow only lasts until the end of the block ({ ... }) it was declared in. When the block ends, the outer variable is visible again, unchanged:

fn main() {
    let temp = 20;
    {
        let temp = temp + 15;
        println!("inside: {temp}");
    }
    println!("outside: {temp}");
}
inside: 35
outside: 20

This surprises people who expected the inner let to change the outer value. If that's what you want, use let mut temp outside and assign with temp = temp + 15; (no let) inside.

When to use it at work

Shadowing is idiomatic for transforming the same value step by step: trimming then parsing input, unwrapping an Option into a plain value (let config = config.unwrap_or_default();), or turning a Vec into a sorted version. It keeps one good name for one concept.

Avoid shadowing a name with something unrelated (let count = "hello"; far from the first count), because readers have to work out which one is live. Clippy has opt-in lints (shadow_unrelated, shadow_reuse) for teams that want to restrict it, but the default style accepts shadowing.

Common mistakes

Example

fn main() {
    let input = "  42  ";
    let input = input.trim();
    let input: u32 = input.parse().expect("not a number");
    println!("parsed {input}, doubled {}", input * 2);

    let mut total = 0;
    for price in [5, 10, 20] {
        let price = price * 2; // a new `price` for this pass only
        total += price;
    }
    println!("total {total}");

    let name = "ferris";
    {
        let name = name.to_uppercase();
        println!("shouting: {name}");
    }
    println!("back to: {name}");
}

Output:

parsed 42, doubled 84
total 70
shouting: FERRIS
back to: ferris

Practice it