Bindings are immutable by default
In many languages you can reassign a variable whenever you like. In Rust, a let binding is immutable: once it has a value, that value is final. This is a safety feature. If a value never changes, then the compiler guarantees it, and a whole category of bugs can't happen.
When a value genuinely has to change, such as a counter or a running total, you opt in with let mut:
let mut score = 0;
score += 10;
println!("{}", score);
This prints 10. The += operator adds to the current value and stores the result back in the same variable.
The classic error
Sooner or later every Rust beginner assigns to a plain let binding:
let clicks = 0;
clicks += 1;
println!("{}", clicks);
The program doesn't compile. The compiler reports error[E0384]: cannot assign twice to immutable variable `clicks`. It marks the declaration as the first assignment, marks the += line as the second, and ends with help: consider making this binding mutable, followed by the corrected line let mut clicks = 0;. The message tells you the fix, so read to the end of it.
Shadowing: a new binding with the same name
There is a second way to give a name a new value, and it needs no mut: write another let with the same name. This is called shadowing. It doesn't change the old value. It creates a fresh binding that hides the old one for the rest of its scope.
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("inner {}", x);
}
println!("outer {}", x);
This prints inner 12 and then outer 6. The second let makes x equal to 6. The block creates a third x equal to 12, which disappears at the closing brace, so the outer x is 6 again.
Shadowing can change the type
Because a shadowing let is a brand-new binding, it can have a different type from the old one. A mut variable can never do that:
let mut label = "42";
label = 42;
The compiler rejects this with error[E0308]: mismatched types and expected `&str`, found integer. A mut variable keeps the type it started with. Shadowing is the usual way to convert a value and keep its name:
let input = "7";
let input: i32 = input.parse().unwrap();
println!("{}", input + 1);
It prints 8. The first input is text. The second is a number made from it, and the text version is no longer reachable.
How parse and unwrap work
parse() turns text into another type. It needs to know which one, so the new binding is annotated with : i32. Without the annotation, let label = label.parse().unwrap(); fails with error[E0284]: type annotations needed.
Text isn't always a number, so parse() returns a Result that is either a value or an error. "abc".parse::<i32>() gives Err(ParseIntError { kind: InvalidDigit }). The call to unwrap() takes the value out of a successful result, and it panics, stopping the program, on an error. Using it here is fine because the text is known to be digits.
Solving the exercise
The expected output is two lines, clicks = 2 and label doubled = 84. The steps follow the two lessons above:
- Declare the counter with
let mut clicks = 0;, since it will change. - Increase it twice with
clicks += 1;, on two lines. No newlet, because it is the same binding. - Print it with
println!("clicks = {}", clicks);. The text before the placeholder is part of the output. - Declare
labelas the text"42". The digits in quotes are a&str, not a number. - Shadow it with
let label: i32 = label.parse().unwrap();. The right side still refers to the old, textlabel; the new one takes over from the next line. - Print the double with
println!("label doubled = {}", label * 2);. The multiplication works becauselabelis now ani32.
let mut clicks = 0;
clicks += 1;
clicks += 1;
println!("clicks = {}", clicks);
let label = "42";
let label: i32 = label.parse().unwrap();
println!("label doubled = {}", label * 2);
The output is:
clicks = 2
label doubled = 84
When to use which
- Use
letby default. Most values never change. - Use
let mutwhen the same variable has to be updated several times, as in a counter. - Use shadowing to convert a value to another type, or to do a step of preparation and keep the name.
Key takeaways
letbindings are immutable, and assigning to one givescannot assign twice to immutable variable.let mutallows reassignment, but the type stays the same.- Shadowing declares a new binding with the same name, and it may have a different type.
- Parse text into a number with
parse(), an explicit type, andunwrap()when you know it will succeed.
