Vibe Engines
YouTube

Lesson 2.2 · 8 min

Values that change

A chat program has to count: how many messages so far. A count that cannot change is not much of a count. Rust makes you ask before a name may change, and the compiler explains what happens if you forget.

Adds to ChatterboxA message counter.

Try to change a name

Chatterbox should say how many messages have been sent. Start the count at zero and add one when a message arrives. Here is the obvious way to write it.

Assigning a second time to a plain name
fn main() {
let message_count = 0;
message_count = message_count + 1;
println!("{} messages so far", message_count);
}
error[E0384]: cannot assign twice to immutable variable `message_count`
 --> src/main.rs:3:5
  |
2 |     let message_count = 0;
  |         ------------- first assignment to `message_count`
3 |     message_count = message_count + 1;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut message_count = 0;
  |         +++

Read the message. "cannot assign twice to immutable variable" says it plainly. Immutable means "cannot change". A plain let gives a name that is set once. The compiler even suggests the fix in its help text: make it mutable.

This is a safety feature, not a nuisance. In a long program, a value that can change anywhere is a value you cannot trust anywhere. So Rust starts every name as fixed, and you opt in to change, one name at a time, with the word mut (short for "mutable").

let mut allows the change
fn main() {
let mut message_count = 0;
message_count = message_count + 1;
message_count = message_count + 1;
println!("{} messages so far", message_count);
}
$ cargo run
2 messages so far
PredictHow many?
fn main() {
let mut count = 10;
count = count + 5;
count = count - 3;
println!("{}", count);
}

What does this print?

  • 12
  • 15
  • 10
Reveal the answer

The program prints: 12 Each assignment uses the current value. 10 becomes 15, then 15 becomes 12. The name count only ever holds the latest value.

A shortcut, and a different trick

count = count + 1 is so common it has a short form: count += 1. Same meaning, fewer characters.

There is also a second way to "change" a name that is not changing at all. You can write let again with the same name. This is called shadowing: the new name sits in front of the old one and hides it. The old value is untouched; you just cannot reach it by that name any more.

Shadowing: a brand new name that happens to match
fn main() {
let count = 1;
let count = count + 1;
let count = count * 10;
println!("{}", count);
}
$ cargo run
20

Which to use? Reach for let mut when one thing really is a running tally, like a counter. Reach for shadowing when you are working a value through steps and the old version is no longer interesting. A rule of thumb: start with plain let, and add mut only when the compiler asks.

Chatterbox v0.2, step two

The counter goes in. It starts at zero, and one message is counted.

src/main.rs: Chatterbox v0.2, step two
fn main() {
let name = "Sam";
let bot = "Chatterbox";
let version = "0.2";
let mut message_count = 0;
println!("{} v{}", bot, version);
println!("Hi {}! I'm {}.", name, bot);
message_count += 1;
println!("{} messages so far", message_count);
}
$ cargo run
Chatterbox v0.2
Hi Sam! I'm Chatterbox.
1 messages so far
Try itYour turn: count the arrivals

Make count_after return the starting count plus the number of new messages. i32 is just "a whole number"; the next lesson explains it. Start from the let mut line already there. You don't need to understand fn yet (chapter 3, lesson 4 explains it): just fill in the body.

Make count_after return the starting count plus the number of new messages. i32 is just "a whole number"; the next lesson explains it. Start from the let mut line already there. You don't need to understand fn yet (chapter 3, lesson 4 explains it): just fill in the body.

fn count_after(start: i32, arrivals: i32) -> i32 {
let mut count = start;
// add the new messages to count
count
}
 
fn main() {
println!("{}", count_after(0, 3));
}

Hidden tests (3): adds_to_existing from_zero nothing_arrives

This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
What does E0384 mean?Reveal
You assigned a second time to a name that was not declared mut. Plain let names are fixed.
How do you allow a name to change?Reveal
Write let mut when you create it.
What is the difference between mut and shadowing?Reveal
mut changes the value held by one name. Shadowing makes a brand new name with the same spelling that hides the old one.

That is lesson 2.2. Tick it off so the course remembers where you are.

Questions people ask

Why is Rust immutable by default?

A value that cannot change is easier to trust when reading code. You opt in to change only where you need it.

What does mut stand for?

Mutable, which means able to change.

Is shadowing the same as changing a variable?

No. It creates a new name. The old value still exists until it goes out of scope.

Learn Rust by building a chat app · Lesson 2.2 · every compiler message above is real output from rustc 1.99.0 (b940084d7 2026-09-28) (edition 2024) · Vibe Engines · 2026