Hello! In our last lesson, we focused on Rust's "immutability by default" philosophy and how to opt into mutability using the mut keyword. This established a clear line between values that can change and those that cannot.
Today, we'll explore a related but distinct concept: shadowing. At first glance, shadowing might seem like a way to circumvent the immutability rules we just learned, but it's a separate mechanism with its own rules and powerful use cases. This lesson will teach you how to use shadowing to rebind variables within a scope and, crucially, understand how it differs from mutation. For someone with your experience in Python and Swift, grasping this distinction is key to writing idiomatic and effective Rust code.
1. What is Shadowing?
In the previous lesson, we saw that this code produces a compile-time error:
let x = 5;
x = 10; // Error! Cannot assign twice to immutable variable `x`.
However, the following code is perfectly valid:
let x = 5;
let x = 10; // This is fine.
This is shadowing. We are not mutating the original x. Instead, we are declaring a new variable, which also happens to be named x. This new variable "shadows" the previous one, meaning that any subsequent use of the name x will refer to this new variable until it goes out of scope or is itself shadowed.
Let's watch a short video that introduces this concept.
Rust Tutorial #3 - Variables, Constants and Shadowing
This clip from Tech With Tim's tutorial demonstrates the basic syntax of shadowing and how it allows you to "recreate" a variable with the same name without using mut.
Please watch from the introduction to shadowing. Notice how using the let keyword a second time is the key to this technique.
2. Shadowing vs. Mutability: Rebinding a Name vs. Changing a Value
This is where your computer science background becomes particularly useful. The distinction between shadowing and mutability is not just semantic; it relates to what is happening in memory.
- Mutability (
let mut): Allows you to change the value stored at a particular memory location. The variable binding remains the same; only the data it points to changes. - Shadowing (
let): Creates a new variable, which may be at a completely new memory location. The namexis rebound to this new variable. The original variable and its data still exist but are now inaccessible by that name in the current scope.
This can feel abstract, so let's look at a fantastic explanation that clarifies this with code. The following Stack Overflow discussion begins with the exact question you might be having: if shadowing exists, why do we need mut?
In Rust, what's the difference between "shadowing" and " ...
This Stack Overflow Q&A provides a precise, technical breakdown of the difference. It's one of the best explanations of this concept.
First, read the question posed by the user in the first section to frame the problem. Next, study the highest-voted answer, which begins here. It brilliantly explains the difference between rebinding a name and changing a value in a storage location. The example using a reference (&x) is particularly insightful, though we'll cover references in detail later. Finally, look at the very short answer that prints the memory addresses. This provides concrete proof that shadowing creates a new variable in a different memory location.
The key takeaway is that shadowing and mutability are fundamentally different operations. Mutability changes data "in-place," while shadowing rebinds a name to an entirely new piece of data.
3. The Power of Shadowing
So, why would you choose shadowing over mutability? It offers two significant advantages.
A. Changing a Variable's Type
This is the most common and powerful use case for shadowing. A mutable variable can have its value changed, but it can never change its type. Shadowing, because it creates a brand new variable, allows you to reuse a name for a value of a different type.
This is incredibly useful for data conversion. Imagine you get some user input as a string, but you need to work with it as a number. Instead of creating differently named variables like input_str and input_num, you can just shadow the original.
The official Rust book provides the canonical example.
Variables and Mutability - The Rust Programming Language
This section from the official documentation explains shadowing and contrasts it with mutability, highlighting the ability to change a variable's type.
Please read from the Shadowing heading down to the end of the first compiler error block. Pay close attention to the spaces example, which transforms a &str into a usize. This is a classic demonstration of shadowing's utility.
To see this in a more complete program, look at the following code. This program asks a user for a number, squares it, and prints the result. Notice how the variable x is shadowed three times, changing its type and value at each step.

Let's break down the journey of x:
let mut x = String::new();:xis a mutableStringto hold user input.let x = x.trim();: The originalxis shadowed. The newxis an immutable&str(a string slice) with whitespace removed.let x: i32 = x.parse()...;: The&stris shadowed. The newxis an immutablei32(a 32-bit integer).let x = x * x;: The integerxis shadowed by a new integerxcontaining the squared value.
This pattern is clean and avoids polluting the scope with intermediate variable names.
B. Scope-Limited Transformations
Shadowing is also tied to scope. A variable is only shadowed within the scope where the new let binding is introduced. Once that scope ends, the shadowing ends, and the name refers to its previous binding.
The Rust book example we looked at earlier demonstrates this perfectly with inner curly braces {}.
fn main() {
let x = 5;
let x = x + 1; // x is now 6
{
let x = x * 2; // In this inner scope, x is 12
println!("The value of x in the inner scope is: {x}"); // Prints 12
}
println!("The value of x is: {x}"); // Back in the outer scope, x is 6
}
This behavior is predictable and allows for temporary transformations without affecting the variable in the outer scope.
Conclusion
In this lesson, we demystified shadowing and positioned it as a powerful feature in the Rust programmer's toolkit. It provides a way to transform data, change types, and reuse names in a clean, immutable-first fashion.
Here are the essential takeaways:
- Shadowing uses the
letkeyword to declare a new variable with the same name as a previous one. - It is fundamentally different from mutability. Shadowing rebinds a name to a new variable (in a new memory location), while
mutchanges the value of an existing variable in-place. - The primary advantage of shadowing is the ability to change a variable's type during transformations, a common pattern when parsing or cleaning data.
- Shadowing is scope-bound; once an inner scope ends, the name reverts to its binding from the outer scope.
In our next lesson, we will explore Rust's control flow constructs like if, else, and loops. You will see that these constructs also introduce scopes, making your new understanding of shadowing even more relevant.