Let’s start with the most fundamental way to bring a value into existence: the let binding. You’ll use this thousands of times, so it’s crucial to get it right. The most important thing to know, and the thing that trips up almost every newcomer from other languages, is this: in Rust, a variable is immutable by default.

When you write let x = 5;, you’re not creating a “variable” in the classic, C-style sense where it’s a named box you can put new things into whenever you want. You’re creating an immutable binding. The name x is now permanently tied to the value 5. It’s a contract. You can look, but you can’t touch. Try to break this contract and the compiler will swat you down with the gentle force of a freight train.

fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6; // Let's try to be clever and change it
    println!("The value of x is: {x}");
}

Go on, try to compile that. I’ll wait. See? The error is wonderfully clear: “cannot assign twice to immutable variable x”. The compiler isn’t just telling you what went wrong, it’s telling you why: you declared x as immutable, you absolute maniac. This is Rust’s number one strategy for ensuring safety. If a value can’t change, a whole category of bugs—especially those related to concurrency where one thread changes data another thread is relying on—simply vanishes.

So You Actually Want to Change It?

Right, so immutability is the default, but we don’t live in a static world. Sometimes you need to change your mind, or more accurately, your value. For this, you use the mut keyword. It’s your way of telling the compiler, “I know what I’m doing. This one gets a hall pass.”

fn main() {
    let mut x = 5; // Notice the 'mut' – it's crucial
    println!("The value of x is: {x}");
    x = 6; // This is now perfectly legal
    println!("The value of x is: {x}");
}

This compiles and runs happily, printing 5 and then 6. The mut keyword is your explicit opt-in to mutability. It’s a signal to anyone reading your code (and most importantly, to the compiler) that this value is intended to change. This explicitness is a feature, not a bug. It makes you think about whether you really need mutability, which often leads to better, safer design. Don’t just sprinkle mut everywhere because you’re used to it from other languages. Ask yourself: “Does this value need to change?” If the answer is no, leave mut off. Your future self, debugging a race condition, will thank you.

The Type System is Watching

Here’s a subtle but important point. mut is about the binding, not the value. It controls whether you’re allowed to reassign the name x to a completely new value. It does not mean the value itself is deeply mutable if it’s a compound type. That’s governed by the ownership and borrowing rules, which we’ll get to later. For now, just remember: mut lets you change what the variable points to.

Also, note that you must declare mutability up front. You can’t just decide later that a variable should be mutable. This is again part of Rust’s philosophy of being explicit and making decisions upfront.

Why This Madness?

You might be wondering why the language designers, in their infinite wisdom, chose this seemingly backward default. It’s not madness; it’s brilliance. Studies of large codebases have shown that the vast majority of variables are assigned to once and never changed. Rust simply makes this common case the default, requiring you to explicitly opt into the less common, more dangerous case of mutability. This drastically reduces the cognitive load when reading code. If you see a binding without mut, you can be 100% certain its value will never change in this scope. That’s a powerful guarantee. It’s one less thing you have to track in your head. The compiler is doing that work for you.

So, the rule of thumb: start without mut. Only add it when the compiler rightly complains that you’re trying to change something. It’s the Rust way.