1.4Variables, mutability, and initialization
In the previous lesson (1.3), we created variables and gave them values. This lesson is about changing those values, and it's the first place Rust's personality really shows.
Assignment
Giving a variable a value at the moment it's created is called initialization (let width = 5;). Giving an existing variable a new value later is called assignment, and it uses =, the assignment operator:
fn main() {
let mut width = 5;
println!("width is {width}");
width = 7;
println!("width is now {width}");
}width is 5
width is now 7
Assignment replaces the old value; the 5 is gone, and width's box in memory holds a 7. So far this matches every language you've ever heard of, except for one small word doing big work on the first line: mut.
Variables are immutable by default
Delete the mut and watch what happens:
fn main() {
let width = 5;
println!("width is {width}");
width = 7;
println!("width is now {width}");
}error[E0384]: cannot assign twice to immutable variable `width`
--> src/main.rs:4:5
|
2 | let width = 5;
| ----- first assignment to `width`
4 | width = 7;
| ^^^^^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut width = 5;
| +++
In Rust, a plain let variable is immutable: once initialized, its value cannot be changed. If you want a variable you can assign to, you must say so up front with let mut (mut as in mutable, changeable). The error above is the compiler holding you to that, and notice it's a good error: it shows where the variable was created, where the rule broke, and the exact edit that fixes it.
New programmers usually meet this rule with polite disbelief. A variable that can't vary? But the default earns its keep twice over.
Key insight
First: most variables never need to change. Look at real programs and you'll find most values are computed once, used, and never touched again; "changes later" is the minority case. Rust makes the common case the default and the special case the marked one.
Second, and bigger: mut is information. When you read let mut attempts, the code is telling you "this value will change, watch it." When you read let price, you can stop tracking it; the compiler guarantees it's the same value everywhere. In a 50-line program this is a nicety. In a 50,000-line program it's a searchlight.
Accidentally assigning to something that shouldn't change is also a classic source of bugs, and in Rust that whole category dies at compile time. You'll notice this is the ownership chapter's pitch in miniature: say what you intend, and the compiler enforces it.
Warning
= is assignment. == (two equals signs) is the comparison operator, "are these equal?", which you'll meet properly in chapter 4. Mixing them up is a rite of passage in most languages, where if (x = 5) silently assigns instead of comparing. File this warning away; when you reach lesson 4.7 you'll learn why Rust turns that classic bug into a compile error too.
Initialization is enforced
Here's the second rule of this lesson, and it kills an entire genre of bug. You can declare a variable without a value:
let x: i32;
(Note we have to state the type now; with no value, there's nothing to infer from.) But try to use it before it has one:
fn main() {
let x: i32;
println!("x is {x}");
}error[E0381]: used binding `x` isn't initialized
--> src/main.rs:3:21
|
2 | let x: i32;
| - binding declared here but left uninitialized
3 | println!("x is {x}");
| ^ `x` used here but it isn't initialized
|
help: consider assigning a value
|
2 | let x: i32 = 42;
| ++++
The rule is precise: Rust doesn't demand a value at the declaration, it demands one before the first use. This compiles fine:
fn main() {
let x: i32;
x = 10;
println!("x is {x}");
}x is 10
(A fine point you can file away: that x = 10 doesn't need mut, because it's the variable's first value. It's initialization arriving late, not re-assignment.)
Key insight
What would happen without this rule? In C++, reading an uninitialized variable is legal to write and gives you whatever garbage bits happened to be in that memory box: a different "value" on different runs, on different machines, sometimes even masked in debug builds. learncpp.com needs a whole lesson on the undefined behavior this unleashes. This course covers the same hazard with one sentence: in Rust, the program that reads uninitialized memory doesn't compile. An entire lesson's worth of horror, refunded.
Quiz time
Question #1
What's the difference between initialization and assignment?
Show solution
Initialization gives a variable its first value. Assignment replaces the value of a variable that already has one, and only mut variables allow it.
Question #2
This is the course's first predict the compiler error question, a genre you'll see often: the program below does not compile. What will the compiler complain about, and what's the fix?
fn main() {
let count = 10;
count = count + 1;
println!("{count}");
}Show solution
count is immutable (no mut), and line 3 assigns to it. The compiler reports error E0384, "cannot assign twice to immutable variable count", points at both the original let and the offending assignment, and suggests the fix: let mut count = 10;.
Question #3
What does this program print?
fn main() {
let mut x = 1;
println!("{x}");
x = 2;
x = 3;
println!("{x}");
}Show solution
1
3
The assignments to 2 and then 3 each replace the previous value, and only the final value is printed. (The compiler does flag the 2 with an unused-assignment warning, since nothing reads it before it's overwritten. It compiles and runs anyway.)
Question #4
Which of these variables needs mut?
let player_name = "Ada";
let high_score = 0;
// ... later in the program:
// high_score = 250;Show solution
Only high_score, because it's assigned again later. player_name is never changed, so the immutable default is exactly right for it. Declaring it mut anyway wouldn't be an error (just a compiler warning about an unneeded mut), but it would tell readers a lie about your intentions.
So variables can change, with permission. Printing them flexibly is next: println! has more tricks than the {x} you've seen so far.