Variable Shadowing and Scope in Rust

In Rust, variable shadowing lets you reuse the same variable name in the same or a nested scope. Each shadowed variable is a new binding, which allows transformations or type changes without using a new name.

When learning how Rust handles variables, one powerful feature to understand is shadowing. This lets you declare a variable with the same name as a previous one, even in the same scope. Combined with scope blocks, this feature allows you to make your code more readable and flexible.

In this post, I will explain what variable shadowing is, how it works inside scopes, and why it is useful in real programs.

If you have not read Variables and Mutability yet, go through that first to understand the basics.

What Is Variable Shadowing?

Variable shadowing happens when you declare a new variable with the same name as an existing one. Rust allows this:

fn main() {
    let x = 5;
    let x = x + 1;
    println!("x is {}", x);
}

Here, x is declared twice. The second declaration shadows the first one. It does not modify the original x, instead, it creates a new binding with the same name.

This works because each let creates a new variable. The old x is still there, but it is no longer accessible.

Why Is Shadowing Useful?

Shadowing helps when you want to change the value or even the type of a variable but keep the same name. This avoids introducing new variable names when you only care about the final result.

Example:

fn main() {
    let spaces = "   ";
    let spaces = spaces.len();
    println!("spaces is now a number: {}", spaces);
}

The first spaces is a &str, and the second one is a usize. This is allowed because shadowing replaces the old variable with a new one.

If you tried to do this with a mutable variable instead, it would not work:

let mut spaces = "   ";
spaces = spaces.len(); // This gives an error

This fails because you are trying to assign a usize to a &str, which are different types. Shadowing solves this by starting fresh.

Scope and Variable Lifetime

Rust uses block-level scoping, which means variables live only inside the {} where they are defined.

fn main() {
    let x = 10;

    {
        let x = x + 5; // shadows x only in this block
        println!("Inner x: {}", x);
    }

    println!("Outer x: {}", x);
}

This prints:

Inner x: 15 
Outer x: 10

The inner x only exists inside the block. Once that block ends, the outer x becomes visible again.

This allows for safe re-use of variable names without risk of confusion.

Shadowing vs. Mutability

Let me compare the two:

FeatureShadowingMutability
Syntaxlet x = x + 1;let mut x = 5; x = 6;
Type ChangeAllowedNot allowed
RedefinitionCreates a new variableModifies the same variable

Use mut when you want to update the same value. Use shadowing when you want to transform the value or type and prefer to reuse the name.

Summary

Rust allows you to shadow variables by redeclaring them with let. This creates a new variable, not a change to the original. Shadowing works well with scope blocks, letting you temporarily override values. It is different from mutability, and it gives you more flexibility with types and transformations.

Next up: Primitive Data Types in Rust