Before continuing this series, a quick disclaimer: I’m a self-taught Rustacean sharing my journey, not a language expert. But here’s the thing—there aren’t many people documenting the actual experience of learning Rust, including the frustrating parts. If documenting my struggles and breakthroughs helps even one person (and reinforces my own learning), that’s a win.
Now, let’s dive into some foundational concepts.
I know the temptation to race ahead to higher-order concepts—those are what let you build real things instead of toy programs. But here’s the truth: those advanced concepts are meaningless without solid foundations. I’ve already covered getting started with a Hello World app and the advantages of constants, so now we’re tackling comments, variables, mutability, and shadowing.
Comments
I’m going to start by asking you to take my advice because I’m not using it. I say that because I’m terrible at commenting my code. Comments are important—don’t be like me. You should liberally comment your code. Think of comments as breadcrumbs to your future self. You could struggle for days on a particular piece of your program, and if you’re diligent with commenting, you’ll have something to come back to later when faced with a similar problem.
Try to make your comments reflect the thinking that got you to the code, not just what the code does.
Single-line comments are prefaced with two slashes:
// This is a Rust comment.
You can stack single-line comments for multiple lines:
// This is the first line...
// ...of a multi-line comment.
Or use the true multi-line syntax:
/* This is a true
multi-line comment
in Rust */
Comments can go anywhere you like, but they’re most useful right around what they’re commenting on:
fn main() {
// Bind an integer to the variable named life, then print
// the value of the meaning of life to the console.
let life = 42;
println!("What is the meaning of life? {}", life);
}
There’s a third type of comment that powers one of Rust’s most amazing features—documentation comments:
/// This is a documentation comment in Rust.
/// These comments are picked up by Cargo's automated documentation tool
/// and support Markdown formatting.
For module-level documentation, use //! instead. I’ll write more about this down the road, but if you want to read more now, check out “Publishing a Crate to Crates.io” in the Rust Book.
Variables, Mutability, and Shadowing
Assigning Variables
Programming languages would be useless without variables. Variables allow the creation of programs that can accept a wide variety of input to do some action. In Rust, creating a variable and giving it a value looks like this:
let album = "Attero Dominatus";
Here we bind a string slice (more about that in another article) to a variable named album, using the let keyword.
Immutability of Variables
If you write code that tries to re-assign the album variable later in your program, you’ll receive a compiler error:
let album = "Attero Dominatus";
// Later in the program...
album = "Carolus Rex"; // ❌ Error: cannot assign twice to immutable variable
A cornerstone of Rust’s design is immutability by default. Variables can’t be changed after assignment unless you explicitly opt-in with mut. This isn’t Rust being difficult—it’s protecting you from a whole class of bugs where values change unexpectedly. When you do need mutability, you’re forced to be intentional about it.
If a variable needs to change, use the mut keyword to make it mutable:
let mut band = "Sabaton";
band = "Powerwolf"; // ✓ This works because we declared it as mutable
This allows you to change the value assigned to the variable band elsewhere in the program. Default immutability is one of Rust’s terrific safety features and helps you protect yourself by making you think carefully about how and when variables need to be modified.
Shadowing of Variables
Rust allows one variable to be shadowed by another. Here’s an example:
let x = 5;
let x = x + 1;
println!("The value of x is: {}", x);
Let’s break it down:
- First, bind the value 5 to the variable named
x - Second, shadow the value of
x, taking the original value (5) and adding one - The value of
xwill be 6 and this gets printed to the console
Why is shadowing useful? It lets you transform a value through multiple steps while keeping the same variable name, without needing mut. It’s particularly handy when converting between types—like parsing a string into a number—while keeping your variable immutable for safety:
let input = "42";
let input = input.parse::<i32>().unwrap();
// Same name, different type, still immutable
The variable is still immutable, even after the completion of any transformations. This is different from marking a variable as mut—shadowing creates a completely new variable that happens to have the same name.
Putting It All Together
Here’s a practical example combining all three concepts:
fn main() {
// Start with an immutable string
let language = "Rust";
// Shadow it to add more context
let language = format!("{} Programming", language);
// Mutable variable for counting
let mut attempts = 0;
attempts += 1;
println!("Learning {}, attempt #{}", language, attempts);
}
Key Takeaways
- Use
//for single-line comments,/* */for multi-line comments - Variables are immutable by default—use
mutto opt-in to mutability - Shadowing with
letcreates a new variable, even with the same name - When in doubt, start with immutable—add
mutonly when the compiler tells you to
Next Steps
Try this exercise: Write a small program that shadows a variable three times, performs a type conversion, and uses at least one mutable variable. Struggle with it. Read the compiler errors—they’re actually teaching you.
For deeper understanding, Chapter 3 of The Rust Programming Language covers these concepts with more examples. But remember: reading is good, typing is better.
Comments