Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

Learn RustChapter 2: Values

Lesson 2.7 · 8 min

Chapter review: Values

Chapter 2 gave Chatterbox things to remember: a name, a counter with a size, a length limit, a quiet switch, a greeting that grows and settings at the top of the file. Before it starts making decisions, check that it all holds together: a mixed quiz, then one bigger exercise that uses the whole chapter.

Adds to ChatterboxNothing new: a mixed quiz on the whole chapter, and a bigger exercise that uses all of it.

What chapter 2 gave you

Chatterbox v0.2 still does the same thing every time it runs, but it now knows things: its name and version live in one place, it counts with a number that says how big it can grow, and it has a limit and a switch ready for the moment it can decide. One line per lesson, each linked back:

  • 2.1 Names for values: let bot = "Chatterbox"; says a value once, and every later use of bot means it. A plain let is fixed for good.
  • 2.2 Values that change: let mut opts in to change; assigning twice without it is E0384. Writing let again with the same name is shadowing, a brand new name that hides the old one.
  • 2.3 Numbers and true/false: i32, usize for sizes, f64 for decimals, bool for yes or no. Whole-number division drops the fraction, and a comparison answers true or false.
  • 2.4 How big a number can get: every whole-number type has a largest value. Going past it panics while you work, and saturating_add stops at the top on purpose.
  • 2.5 Text: a &str is a view of text you can read; a String is text you own and can grow with push_str. len() counts bytes, not letters.
  • 2.6 Pairs and constants: a const is a fixed fact the whole program can see, with a capital name and a stated type. A tuple keeps a few values together, read by position with .0 and .1 or pulled apart with let (a, b).

Mixed quiz

Eight short programs, all new, with the chapter's ideas shuffled: a name, then text, then a number, then a pair, and round again. Some do not compile, one compiles and then stops while running, and the rest print something exact. Decide which kind each one is before you choose. Every answer was produced by the real compiler.

PredictFlip the quiet switch
fn main() {
let quiet = false;
println!("quiet: {}", quiet);
quiet = true;
println!("quiet: {}", quiet);
}

Does this compile — and if it does, does it run?

  • It compiles and runs
  • Compile error E0384
  • It compiles, then panics
Reveal the answer

It does not compile: E0384. A name made with plain let is set once. Line 4 tries to give quiet a second value, so the compiler refuses with E0384, "cannot assign twice to immutable variable", and its help line suggests let mut quiet. Lesson 2.2 is the one to revisit.

error[E0384]: cannot assign twice to immutable variable `quiet`
 --> src/main.rs:4:5
  |
2 |     let quiet = false;
  |         ----- first assignment to `quiet`
3 |     println!("quiet: {}", quiet);
4 |     quiet = true;
  |     ^^^^^^^^^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
2 |     let mut quiet = false;
  |         +++
PredictA reply that grows
fn main() {
let mut reply = String::from("ok");
reply.push_str(", café");
println!("{}", reply.len());
}

What does this print?

  • 8
  • 9
  • 2
Reveal the answer

The program prints: 9 push_str adds , café to the end, so the String holds ok, café: eight letters, counting the comma and the space. But len() counts bytes, and the accented é takes two, so the answer is 9. Lesson 2.5 has both halves of this.

PredictBytes per message
fn main() {
let total_bytes: u32 = 23;
let messages_seen: u32 = 4;
println!("about {} bytes each", total_bytes / messages_seen);
}

What does this print?

  • about 5 bytes each
  • about 5.75 bytes each
  • about 6 bytes each
Reveal the answer

The program prints: about 5 bytes each Both numbers are whole numbers, so the answer is a whole number too. 23 divided by 4 is 5 with 3 left over, and the leftover is dropped, not rounded up. To keep the fraction you need decimal numbers such as f64 (lesson 2.3).

PredictA version with three parts
const VERSION: (u32, u32) = (0, 2, 1);
 
fn main() {
println!("v{}.{}", VERSION.0, VERSION.1);
}

Does this compile — and if it does, does it run?

  • It compiles and runs
  • Compile error E0308
  • It compiles, then panics
Reveal the answer

It does not compile: E0308. The type says a pair, (u32, u32), and the value has three parts. A tuple always has exactly as many parts as its type says, so this is a value of the wrong type: E0308, "expected a tuple with 2 elements, found one with 3 elements". Lesson 2.6 covers tuples.

error[E0308]: mismatched types
 --> src/main.rs:1:29
  |
1 | const VERSION: (u32, u32) = (0, 2, 1);
  |                ----------   ^^^^^^^^^ expected a tuple with 2 elements, found one with 3 elements
  |                |
  |                expected because of the type of the constant
  |
  = note: expected tuple `(u32, u32)`
             found tuple `(u32, u32, {integer})`
PredictBelow zero
fn main() {
let mut unread: u8 = 1;
unread -= 1;
unread -= 1;
println!("unread: {}", unread);
}

Does this compile — and if it does, does it run?

  • It compiles and runs
  • It compiles, then panics
  • Compile error E0384
Reveal the answer

It compiles, then panics at run time. It compiles, because mut allows the change. The first subtraction takes unread to 0, the smallest u8; the second asks for minus one, which a u type has no pattern for. The program stops with attempt to subtract with overflow: the edge at the bottom works just like the one at the top (lesson 2.4).

PredictThe same name, three times
fn main() {
let message = "hello";
let message = message.len();
let message = message * 10;
println!("{}", message);
}

What does this print?

  • 50
  • 5
  • hello
Reveal the answer

The program prints: 50 Each let makes a brand new name that hides the one before, so no mut is needed. The second is the length of hello, 5 bytes, a number now and not text; the third is that times 10. Shadowing can even change the type, which mut cannot (lesson 2.2).

PredictPull the pair apart
fn main() {
let last = ("Sam", 3);
let (name, sent) = last;
println!("{} has sent {}", name, sent + 1);
}

What does this print?

  • Sam has sent 4
  • Sam has sent 3
  • 3 has sent Sam
Reveal the answer

The program prints: Sam has sent 4 Destructuring matches by position: name takes the first part, "Sam", and sent the second, 3. The second hole is filled with sent + 1, which is worked out first, so it prints 4. The tuple itself still holds 3 (lesson 2.6).

PredictExactly at the limit
const MAX_LEN: usize = 200;
 
fn main() {
let length: usize = 200;
let quiet = false;
println!("{} {}", length <= MAX_LEN, !quiet && length < MAX_LEN);
}

What does this print?

  • true true
  • true false
  • false false
  • false true
Reveal the answer

The program prints: true false The first hole asks "at most the limit?", and 200 is, so true. The second asks "not quiet and under the limit?": not quiet is true, but 200 is not less than 200, and one false side makes && false. The difference between < and <= is the whole question (lesson 2.3).

Milestone: a status line

Now the chapter all at once. Chatterbox will one day print a status line after each message: who it is, how many messages it has seen, and whether the last one was within the limit. The settings are already at the top of the file as constants. Write three functions: a counter that can never overflow, a check that a message's length is allowed, and the line itself. Read each function's first line and the comment above it: they are the whole specification, and the hidden tests check the edges, so mind every < and <=.

Try itYour turn: Chatterbox's status line

count_after(0, 3) is 3, and near the top of a u32 it stops at the largest value instead of panicking. fits(200) is true, while fits(0) and fits(201) are false. status(3, "hello") is Chatterbox v0.2 | 3 seen | last: 5/200 bytes | fits: true, built from the constants, not typed out. Replace each todo!().

count_after(0, 3) is 3, and near the top of a u32 it stops at the largest value instead of panicking. fits(200) is true, while fits(0) and fits(201) are false. status(3, "hello") is Chatterbox v0.2 | 3 seen | last: 5/200 bytes | fits: true, built from the constants, not typed out. Replace each todo!().

// Chatterbox's settings, as chapter 2 left them.
const BOT: &str = "Chatterbox";
const VERSION: (u32, u32) = (0, 2);
const MAX_LEN: usize = 200;
 
/// The count after more messages arrive. It never panics: if the total
/// would go past the largest u32, it stays at the largest u32.
fn count_after(messages_seen: u32, more: u32) -> u32 {
todo!()
}
 
/// true when a message this many bytes long is allowed:
/// not empty, and no longer than MAX_LEN.
fn fits(length: usize) -> bool {
todo!()
}
 
/// A status line about the last message, like
/// "Chatterbox v0.2 | 3 seen | last: 5/200 bytes | fits: true"
fn status(messages_seen: u32, last: &str) -> String {
todo!()
}
 
fn main() {
let messages_seen = count_after(0, 3);
println!("{}", status(messages_seen, "hello"));
}

Hidden tests (8): an_empty_message_does_not_fit count_adds count_sticks_at_the_top one_byte_over_does_not_fit exactly_at_the_limit_fits the_status_counts_bytes the_status_line the_status_of_an_empty_message

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

Where this leaves you

If the quiz went well and the tests are green, you have chapter 2: you can name a value, let it change only when it must, choose a number type by what it holds and how big it can get, tell a view of text from text you own, and put a program's fixed facts at the top of the file. Notice what fits could not do: it can say false, but nothing in Chatterbox can act on that yet. Chapter 3 is where a true or false starts choosing what happens next.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
What is the difference between <code>let mut</code> and writing <code>let</code> again with the same name?Reveal
let mut lets one name hold a new value later, of the same type. Writing let again is shadowing: a brand new name that hides the old one, and it may even have a different type.
A <code>u8</code> holding 0 has 1 taken away, in a program started with plain <code>cargo run</code>. What happens, and how would you stop at the edge on purpose instead?Reveal
It panics with "attempt to subtract with overflow", because a u type has no negative values. To stop at the edge on purpose, use a saturating function such as u8::saturating_sub, the subtracting twin of saturating_add.
Why are Chatterbox's name, version and length limit constants, while its message counter is a <code>let mut</code>?Reveal
The first three are fixed facts known before the program runs, so they belong in a const every function can see. The counter changes while the program runs, so it is a name made with let mut.
What does <code>"héllo".len()</code> give, and why not 5?Reveal
6. len() counts bytes, and the accented letter takes two bytes.
Your Chatterbox so far: v0.2 (after lesson 2.6)
Chatterbox v0.2 after lesson 2.6: the whole program
src/main.rs
// Chatterbox: a small chat program, built one lesson at a time.
 
/// What the bot is called. A constant: fixed for the whole program.
const BOT: &str = "Chatterbox";
/// The version as a pair of numbers: (major, minor).
const VERSION: (u32, u32) = (0, 2);
/// The longest message we accept, in bytes.
const MAX_LEN: usize = 200;
 
/// The first line Chatterbox prints.
fn banner() -> String {
format!("{} v{}.{}", BOT, VERSION.0, VERSION.1)
}
 
/// The greeting, with a name filled in.
fn greeting(name: &str) -> String {
let mut text = format!("Hi {}! I'm {}.", name, BOT);
text.push_str(" Ask me anything.");
text
}
 
fn main() {
let name = "Sam";
// u32: a whole number that is never negative and fits in 4 bytes.
let mut messages_seen: u32 = 0;
let quiet = false; // true would skip the greeting
 
println!("{}", banner());
println!("{}", greeting(name));
println!("(limit: {} bytes a message, quiet: {})", MAX_LEN, quiet);
 
messages_seen += 1;
println!("messages so far: {}", messages_seen);
}

Download this version (zip)Unzip, then cargo run inside the folder.

That is lesson 2.7. Tick it off so the course remembers where you are. That is also the end of chapter 2: a few minutes with the course flashcards now makes it stick, and the chapter’s cheat sheet has every idea on one screen.

Next: 3.1 Choosing with if →

Questions people ask

What should I know after the values chapter of a Rust course?

How to name a value with let and allow change with let mut, how shadowing differs from mut, which number type to pick and what happens at its largest value, how a bool comes from a comparison, the difference between String and &str, and when to use a const or a tuple.

Is integer overflow a compile error or a crash in Rust?

Usually neither while you work: if the compiler cannot see the overflow in the code itself, a debug build panics at run time with attempt to add or subtract with overflow. Only an overflow made entirely of numbers written in the code is refused at compile time.

I got some quiz answers wrong. Should I go on to chapter 3?

Read the explanation under each wrong answer and revisit the lesson it names. If the milestone exercise passes its tests, you are ready; chapter 3 uses comparisons, String and constants on every page.

Why does len() not count letters in Rust?

Rust stores text as UTF-8, where a plain English letter takes one byte but an accented letter or an emoji takes more. len() reports the number of bytes, which is what the computer stores; counting letters is a separate, slower job.

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