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:
- The type can change. A shadowing
letcan give the name a value of a different type. This is the idiomatic way to convert input: read aString, then shadow it with the parsed number. You don't need two names likeage_strandage. - The result is still immutable. After
let total = total + tax;the newtotalcan'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
- Writing
letinside a loop to update a total.let total = total + x;inside aforbody shadows only for that one pass; after the loop, the outertotalis unchanged. Uselet mut totaloutside andtotal += x;inside. - Forgetting
let.level = level + 1;withoutletis an assignment, which needsmut(error E0384).
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