Rust's Variable Shadowing: Re-binding, Not Mutating
Shadowing lets you declare a new variable with the same name, making the old one inaccessible. It's used to transform a value, like changing its type, without making it mutable. The footgun is confusing shadowing (`let x = ...`) with reassignment (`x = ...`).
WHY IT EXISTS: Shadowing allows for convenient, readable transformations of data. Instead of creating slightly different variable names like input_str and input_num, you can reuse the same name for the value as it evolves. This avoids making variables mutable when they don't need to be, adhering to Rust's preference for immutability.
THE MENTAL MODEL: Think of shadowing as placing a new label over an old one on a file folder. The old label is hidden, but not destroyed. In a new, inner scope (like inside curly braces), you can place a temporary label. Once you leave that scope, the temporary label is removed, revealing the one from the outer scope again.
HOW IT WORKS: Using the let keyword on a name that's already in scope creates a completely new variable. This new variable can even have a different type than the one it shadows. The original variable becomes inaccessible for the rest of the current scope. Unlike mutation (using let mut and reassigning), which changes the value in the same memory location, shadowing can allocate new memory for the new variable.
WHEN TO USE IT: Use shadowing when you want to perform a few transformations on a value but treat it as immutable between steps. It's perfect for changing a variable's type, such as parsing a string from user input into a number. For example: let guess = "42"; let guess: u32 = guess.parse().expect("Not a number!");.
WHEN NOT TO USE IT: Avoid shadowing if you need to modify a value many times, especially in a loop. A mutable variable (let mut) is the correct tool for that job, as it's more efficient and clearly signals that the value is expected to change repeatedly. Overuse in complex functions can make it hard to track which version of a variable you're using.
ONE CANONICAL EXAMPLE: A common pattern is processing a value through several steps. First, declare a string with extra whitespace: let x = " 5 ";. Then, shadow it with a trimmed version: let x = x.trim();. Finally, shadow it again with a parsed integer type: let x: i32 = x.parse().unwrap();. You now have a clean integer x with the value 5, having used the same name through three different types and values without requiring mutability.
Read the original → doc.rust-lang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.