---
title: "5.2 Shadowing"
description: "Rebinding a name with let, why shadowing isn't mutation, and the type-changing idiom Rust programmers use everywhere."
url: "https://learnrust.net/chapter-5/shadowing/"
last_updated: "2026-06-12"
---

# 5.2 Shadowing

Lesson [1.6](https://learnrust.net/chapter-1/reading-input/) cleaned up user input like this, and promised something tidier later:

```rust
let trimmed = name.trim();
println!("Hello, {trimmed}!");
```

It works, but it leaves two variables in scope for one idea: `name` (the raw input, newline and all) and `trimmed` (the version you actually want). Nobody should use `name` again after the trim, yet there it sits, available, one autocomplete away from a bug. Later is now; here's the tidier idiom:

```rust
let name = name.trim();
println!("Hello, {name}!");
```

A second `let` with the same name. This is called **shadowing**: the new variable *shadows* the old one, and from this line on, the name `name` refers to the new variable. The raw, untrimmed original hasn't been changed or destroyed; it's merely unreachable, standing politely behind its replacement. That's exactly the property we wanted: the dangerous untrimmed version can no longer be touched, *by construction*.

## Shadowing is not mutation

This looks like it breaks lesson [1.4](https://learnrust.net/chapter-1/variables-mutability-initialization/)'s immutability rules, so let's be precise about why it doesn't. Compare:

```rust
fn main() {
    let x = 5;
    x = x + 1; // assignment to an immutable variable
    println!("{x}");
}
```

That's the familiar `E0384` (cannot assign twice to immutable variable), with the compiler suggesting `mut`. But:

```rust
fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;
    println!("{x}");
}
```

```
12
```

This compiles without complaint, because nothing was ever assigned to. Three variables were created; each happened to be named `x`; each `let` computed its value from the one before and then stepped in front of it. Mutation changes the contents of one box. Shadowing brings a new box and hangs the old label on it.

The distinction stops being philosophical at a block boundary. Lesson [1.11](https://learnrust.net/chapter-1/expressions-and-statements/) showed that blocks nest scopes, and a shadow only reaches as far as its scope:

```rust
fn main() {
    let x = 1;
    {
        let x = 10;
        println!("inner: {x}");
    }
    println!("outer: {x}");
}
```

```
inner: 10
outer: 1
```

Inside the block, the inner `x` shadows the outer. When the block ends, the inner `x` is gone and the original steps back out, value intact, exactly as it would have if the inner variable had been named `y`. Had line 4 been a real assignment to a `mut x`, the second print would say `10`. That's the test: mutation survives the block, shadowing doesn't.

> **Warning**
>
> That same property makes a classic trap. Write `let x = 10;` inside a block when you *meant* to assign to the outer `x`, and the program compiles, runs, and quietly ignores your update at the end of the block. If a value seems to "reset itself" after a block, look for an accidental `let`.

## The shadow can change type

Here's what `mut` can't do. A mutable variable is still one box, with one type, fixed at birth:

```rust
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`
```

The variable holds text; you can't assign a number into it, however mutable it is. Shadowing has no such restriction, because each `let` is a brand-new variable that's free to have a brand-new type:

```rust
fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
    println!("{spaces}");
}
```

```
3
```

The first `spaces` is text; the second is a number (`usize`, text lengths being memory measurements, per lesson [4.3](https://learnrust.net/chapter-4/isize-usize-and-integer-literals/)); the *idea* stays "the spaces, in whatever form I currently need." One name tracking one concept across changing types is what shadowing is for, and it's why the idiom is everywhere in real Rust.

It's also quietly present in `let name = name.trim();` from the top of this lesson. The original `name` is a `String`; the trimmed shadow is something else (the compiler error in that `mut spaces` transcript already leaked its name: `&str`). What that type is, and why trimming produces it, is precisely the next two lessons.

## Quiz time

**Question #1**

What does this print?

```rust
fn main() {
    let score = 10;
    let score = score + 5;
    {
        let score = 100;
        println!("{score}");
    }
    println!("{score}");
}
```

<details class="solution">
<summary>Show solution</summary>

```
100
15
```

The middle `let` makes the function-level `score` 15 (shadowing the original 10). The block's `score` is 100 only inside the block; afterward the 15 steps back out. Nothing here needed `mut`, and nothing was mutated.

</details>

**Question #2**

Predict the compiler's reaction:

```rust
fn main() {
    let mut answer = 42;
    answer = "forty-two";
    println!("{answer}");
}
```

Then state the one-keyword-sized change that makes it compile.

<details class="solution">
<summary>Show solution</summary>

`error[E0308]: mismatched types`: the assignment tries to put a `&str` into a variable whose type is integer ("expected integer, found `&str`", with the `let` line flagged as "expected due to this value"). `mut` permits new *values*, never new *types*.

Change `answer =` to `let answer =` (and the now-pointless `mut` should go too): a shadow may be any type it likes, so the program compiles and prints `forty-two`.

</details>

**Question #3**

This compiles and works. Rewrite it with shadowing so that exactly one variable name appears in the function, and explain what got safer:

```rust
use std::io;

fn main() {
    println!("What's your favorite word?");

    let mut raw = String::new();
    io::stdin()
        .read_line(&mut raw)
        .expect("failed to read input");

    let cleaned = raw.trim();
    println!("{cleaned} has {} letters.", cleaned.len());
}
```

<details class="solution">
<summary>Show solution</summary>

```rust
use std::io;

fn main() {
    println!("What's your favorite word?");

    let mut word = String::new();
    io::stdin()
        .read_line(&mut word)
        .expect("failed to read input");

    let word = word.trim();
    println!("{word} has {} letters.", word.len());
}
```

One concept, one name. The safety gain: after the shadow, the raw newline-bearing input is unreachable, so no later line can accidentally use the uncleaned version. (With `raw` and `cleaned` both in scope, that mistake stays available forever. Also, `len()` counting *bytes* rather than letters is a wrinkle the next lesson confesses to properly.)

</details>

You've now twice brushed against the mystery type that `trim` produces. Next lesson: the text type you've been creating since lesson 1.6. The lesson after: the one you've been creating since lesson 0.8 without knowing it.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
