Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
12: Shadowing
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:
- Start with the string
"$12.50". - Shadow it to remove the
$sign (Hint: use.replace("$", "")). - Shadow it again to parse the cleaned string into a
f64. - 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
}There are no comments for now.