Rust for Beginners: Build a Word Game · lab 03 of 7

[ free ] [ loops ] [ ~30 min if you have programmed · ~90 if you have not ]
03

Make it a game you can lose

[ preview ] The lesson is free to read — building it needs a free account.

You'll learn: how to make the computer repeat something — a fixed number of times, or until something happens — and how to stop, skip, and count while it does. By the end of this lab your game has real stakes: six guesses, and then it's over.

Your game takes one guess and answers it. Nobody loses a one-guess game, and a game you cannot lose has no tension. What is missing is repetition — ask, answer, ask again, up to six times, stopping early if the player wins. Rust has three ways to say "do this again"; this lab needs two of them, and uses each where it fits best.

for — do something once per item

The workhorse. A for loop runs its body once for each item in a group of values — the letters in a word, the numbers in a count — and hands you the item each time around. Programmers call such a group a collection; lab 04 builds one from scratch.

for c in guess.chars() {
    println!("one letter: {c}");
}

Read it aloud: for each character c in the guess's characters, run the block. If guess is "crane", the body runs five times, and c is 'c', then 'r', then 'a', 'n', 'e'.

Two new things are hiding in those three lines.

First, .chars(). A &str is text, and Rust will not let you loop over text without saying what a "step" is — one character? one byte? The compiler makes you choose. Try to write for c in guess and the compiler stops you with:

error[E0277]: `&str` is not an iterator
  |
2 |     for c in guess {
  |              ^^^^^ `&str` is not an iterator; try calling `.chars()` or `.bytes()`

Read the line the carets point at — the compiler names both fixes. For this game the answer is always .chars(): we care about letters.

Second, c has a type you never wrote: char. A char is one character, and it is written with single quotes — 'a' is a char, "a" is a string with one character in it. They are different types and Rust will not mix them up for you. Characters come with useful questions built in, and this lab needs exactly one:

c.is_ascii_lowercase()   // true for 'a' through 'z', false for everything else

return works from inside a loop

Here is the shape you will write today. To answer "is every character a lowercase letter?", walk the characters and bail out the moment one fails:

pub fn is_letters(guess: &str) -> bool {
    for c in guess.chars() {
        if !c.is_ascii_lowercase() {
            return false;
        }
    }
    true
}

return false; does not mean "stop the loop" — it means "stop the whole function, the answer is false". The loop dies with it. And if the loop finishes without ever hitting that return, every character passed the test, so the last line of the function answers true.

That last line has no return and no semicolon, on purpose: from lab 01, the last expression of a function is its answer. You could also write return true; there — it means the same thing. This course uses return only for leaving early, and a bare last line for the function's answer.

One edge to know about: if guess is empty, the loop body never runs — with no characters, nothing can fail the test, so the function answers true. That sounds like a bug, but it is not one here: the length check from lab 02 already refuses an empty guess, and this lab wires the two checks together so each one only has one job.

&& — both must be true

A guess the game accepts has the right length and contains only letters. Rust spells "and" as &&, and both sides of it are yes-or-no questions. Two more questions you can ask of numbers, both new today: a > b (is a bigger?) and a >= b (is a bigger, or the same?). Joined with &&:

turn > 0 && max_guesses >= turn

Read it: the turn has started, and the limit still covers it. The whole expression is true only when both sides are. Today's step 3 joins two questions this way — and you wrote both of them yourself.

The turn counter needs >= on its own, too: six guesses allowed and six used means nothing is left, so the limit itself counts as over.

&& has a twin, ||, Rust's or: true when either side is. Nothing in this lab needs it — lab 07's finished game is where you will write one.

Using what lab 02 built: use crate::

is_right_length lives in guess.rs — a different file from the one you are writing today. Inside your library, other modules are reached through crate, which means this library, from the top:

use crate::guess::is_right_length;

Read it right to left: the function is_right_length, inside the module guess, inside crate — your own library. After that line, the function is usable by its short name for the rest of the file. (In main.rs you have been writing use lantern::... instead — same idea, but from outside the library you name it like any other crate, and from inside you say crate.)

Numbers can be a collection too: ranges

Sometimes you don't have a collection — you have a count. "Draw five underscores" has no list to walk. Rust writes a count as a range:

for _ in 0..3 {
    println!("tick");
}

That prints tick three times. 0..3 is the numbers from 0 up to but not including 30, 1, 2: three numbers, three laps. The count can be a name as easily as a literal: 0..word_len is the numbers from 0 up to but not including word_len, and for a 5-letter word that is 0, 1, 2, 3, 4 — five numbers, so the body runs five times. The name _ (an underscore, fittingly) is how you tell Rust I know the loop hands me a value each time; I'm not going to use it. Name it i instead and the compiler warns you about an unused variable — _ is the polite way to say "on purpose".

If you have written C, Java or JavaScript, your fingers may type for (i = 0; i < n; i++). Rust has no such form: it is always for x in 0..n or for c in guess.chars(). This is the one mistake where the compiler is unusually little help — typed from habit, it says only:

error: expected one of `)`, `,`, `@`, `if`, or `|`, found `=`

No error code, no help: line — if you see it pointing at a for line, the fix is rewriting the line in Rust's shape.

while — repeat until something changes

A for loop knows in advance how many times it will run. The game's main loop does not: it ends when the player wins or runs out of turns, and you cannot know which turn that will be. That is a while loop:

while !out_of_turns(used, max_guesses) {
    // one whole turn happens here, and somewhere in it, `used` changes
}

while checks its condition before every lap — one lap is one run through the body: still true, run the body again; false, walk away. The one rule a while loop depends on is that something in the body must be able to change the condition — here, taking a turn adds one to used. Forget that and the loop runs forever; your terminal appears to hang, and Ctrl+C is how you get out.

(Rust has a third loop, spelled loop { }, which repeats until a break; stops it. break works in any loop — you will type one in this lab's shell code, and that is all this course needs it for.)

continue — skip the rest of this lap

Inside any loop, continue; means stop this lap here and start the next one. It is exactly what an invalid guess needs: don't count it, don't score it, go back and ask again.

if !is_valid(&guess, word_len) {
    println!("{word_len} letters, a-z only. Try again.");
    continue;
}
// only a valid guess ever reaches this line

This is a small kindness with a big effect on how the game feels: a typo costs the player nothing. The turn counter only moves for guesses the game accepted.

That's the lesson — free to read. Create a free account to build it: the brief, the grading table and the hint ladder are on the Lab tab.

Create a free account →

Free to start · no credit card · Rust for Beginners is free

The brief, the grading table and the hint ladder live here.

Create a free account to unlock this lab.

Create a free account →

Free to start · no credit card · Rust for Beginners is free

Chat with Guru about the check that is failing you — the written hint ladder on the Lab tab is free and unlimited either way.

Create a free account to unlock this lab.

Create a free account →

Free to start · no credit card · Rust for Beginners is free

Your submitted diff lands here with comments on the exact lines.

Create a free account to unlock this lab.

Create a free account →

Free to start · no credit card · Rust for Beginners is free