Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

One thing I see constantly when I'm reviewing Rust code from people coming from Python or JavaScript is the assumption that shadowing is just a "shorthand" for mutability. They think that writing let x = 5; let x = 6; is functionally identical to writing let mut x = 5; x = 6;. It looks the same on the surface, but under the hood, they are completely different operations.

Shadowing isn't just "mut" in disguise

If you use mut, you are telling Rust that the value stored in that specific memory location is allowed to change. However, the type of that variable is locked in for its entire life. You can't start with an integer and suddenly decide that the same mutable variable should hold a string.

// This will fail to compile
let mut data = "100"; 
data = 100; // Error: expected &str, found integer

Shadowing is different. When you use the let keyword again, you aren't updating a value; you are creating a brand new variable that happens to share the same name as the old one. The old variable still exists in a sense, but it's "shadowed"—it becomes inaccessible. This is a powerful distinction because it allows you to change the type of a variable while keeping the name clean.

Changing types without polluting your namespace

I use shadowing most often when I'm dealing with raw input that needs to be transformed. Imagine you're taking a string from a user and you need it to be a number to do any actual math. Without shadowing, you'd end up with a bunch of clumsy variable names like input_str, input_int, and input_final.

fn main() {
    // We start with a string
    let guest_count = "42"; 
    
    // We shadow the variable to transform it into an integer
    // Notice we use 'let' again!
    let guest_count: i32 = guest_count.parse().expect("Please type a number!");
    
    println!("We need {} chairs.", guest_count);
}

In the example above, I didn't need guest_count_str. Once I parsed the string into an integer, I had no more use for the string version. By shadowing, I kept the variable name guest_count throughout the logic, but shifted the type from a string slice to an integer. It makes the code read much more naturally.

Choosing between mut and shadowing

You might be wondering, "Why not just use mut for everything?" Well, mut signals that a value is going to change over time, usually within a loop or a complex state change. Shadowing, on the other hand, is about transformation.

I generally follow this rule of thumb: if I'm performing a series of transformations on a piece of data (like cleaning a string, then parsing it, then calculating a result), I use shadowing. If I have a counter or a list that I'm adding items to, I use mut. Shadowing also gives you a safety net—because the new variable is immutable by default, you can't accidentally change it later in the function unless you explicitly mark the shadowed version as mut.




📋 Practical Task

Exercise: The Currency Sanitizer

You are building a small tool to process price data from a messy API. The API sends the price as a string with a currency symbol (e.g., "$12.50"), but you need it as a f64 to calculate a discount.

Your Task: Fix the following code using shadowing. Do not use mut. Transform the raw_price variable through the following stages:

  1. Start with the string "$12.50".
  2. Shadow it to remove the $ sign (Hint: use .replace("$", "")).
  3. Shadow it again to parse the cleaned string into a f64.
  4. Finally, print the result multiplied by 0.9 (a 10% discount).
fn main() {
    let raw_price = "$12.50";

    // Your shadowing logic goes here:
    
    // Final output should be: The discounted price is: 11.25
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.