You'll learn: how Rust holds many values under one name — the array, the
slice &[&str] your functions will take, and reaching a word by its position.
One % sign turns any number into a position that is always on the list, and
the same number always lands on the same word.
You already know the secret word
Your game plays by its own rules now: it counts turns, refuses junk, and can be
lost. But you wrote let secret = "crane"; with your own hands. You cannot be
surprised by a word you typed. For the game to surprise its own author, the
secret has to come from somewhere bigger than one line: a list.
A list with a fixed length: the array, and the slice that borrows it
The simplest list in Rust is the array — a row of values, all the same type, whose length is fixed when you write it:
let mini = ["apple", "berry", "crane"];
Three &strs in a row. You reach one by its position — its index — in
square brackets, and positions start at zero:
mini[0] // "apple"
mini[1] // "berry"
mini[2] // "crane"
The consequence that matters today: a list of three has indexes 0, 1, 2
— and no index 3. Reach for a position worked out while the program runs,
past the end —
let wanted = mini.len() + 4; // 7, worked out while the program runs
mini[wanted]
— and the program stops on the spot with a message that names both numbers:
index out of bounds: the len is 3 but the index is 7
Rust calls that a panic — a deliberate stop at the line that went wrong,
instead of quietly reading someone else's memory. Write mini[3] literally and
it never reaches a run at all: the compiler sees the answer from where it
stands and refuses, with error: this operation will panic at runtime. You
will make both impossible in a moment, with math.
Your functions will not take an array directly, though — an array's length is
part of its type, and you do not want a pick that only works on lists of
exactly 64. The type your functions take is the slice:
words: &[&str]
Read it as a borrowed view of some &strs in a row — however many there are.
An array is lent as a slice by writing & in front of it:
let view: &[&str] = &mini; // the same three words, seen as a slice
The test list you are about to write is one line of that shape, with the array
spelled out in place of mini — the project prints it. The course's own word
lists are already slices, so they go in as they are. A slice knows its own
length, words.len() — the fact everything below stands on.
The array's sibling is Vec, the list that can grow. The word list never
changes size, which is why it ships as a fixed list instead; you build your
first Vec next lab.
% — the wheel that always lands on the list
Here is the lab's one genuinely clever line. You want puzzle number 7 — or
7_000_003, and Rust lets you space out a long number with _ — to pick a
word from a 64-word list, every number landing somewhere on the list, the same
number always landing on the same word.
The % operator gives the remainder after division. 7 % 3 is 1,
because 7 divided by 3 is 2 with 1 left over. Two facts make it perfect here:
- the remainder of dividing by
lenis always0up tolen - 1— exactly the valid indexes, so the panic above becomes impossible by construction, as long as the list has at least one word in it; - it is not random.
41 % 3is2today, tomorrow, and on your friend's machine.
So, on the three-word mini above:
7 % mini.len() // 1 — and mini[7 % mini.len()] is "berry"
Any number, wrapped by % mini.len(), comes out as 0, 1 or 2: around the
list like a wheel, 0, 1, 2, 0, 1, 2, …. pick is that wheel on the list it
is handed, and the project prints its one line, because two marks in it are
new. The same answer every time sounds like the opposite of what a guessing game
wants, but look at what it buys: tests can know the answer (pick of puzzle 1
on a 3-word list is the second word, assertable), and two players who type
cargo run -- 42 race the same word. The dice live in main.rs, which turns
the clock into a puzzle number when you don't supply one. The library always
gives the same answer; the luck lives in main.rs.
as — two kinds of number, one bridge
Why (puzzle as usize)? The puzzle number arrives as a u64 — a 64-bit
unsigned integer, big enough for a clock reading. But Rust indexes lists with
usize — the machine's own size-of-things type. They are different types, and
Rust never converts numbers behind your back; mixing them is a compile error,
not a rounding surprise. Try puzzle % words.len() and the compiler answers
with three errors — one for each place the two types meet. The one that names
the problem is:
error[E0277]: cannot calculate the remainder of `u64` divided by `usize`
as is the explicit bridge: puzzle as usize says treat this number as that
type; I know what I'm doing.
Walking a list
The for loop from lab 03 walks a list exactly as it walked characters — one
lap per entry, the loop's name holding the current one:
for word in &mini {
println!("{word}");
}
Three laps, three lines printed: apple, berry, crane. The & lends the
array as a slice, so this is the same walk your function makes over the slice
it is handed. is_known is this walk with a question inside it; the project
says which question, and names the lab where you wrote the shape. One wrinkle
— a borrowed list hands you each entry borrowed too, so comparing an entry
with the guess needs one extra mark. Nothing in this course derives that mark,
so the project prints that one line.
The word lists themselves
Open game/lantern/src/wordlist.rs; it shipped finished and you never edit it.
Two constants, two standards:
WORDS— sixty-four common five-letter words: the secrets, the words the game can pick. Short and curated on purpose, because a secret has to be a word worth guessing toward.ACCEPTED— every secret, plus over a thousand more common five-letter words: the guess dictionary. A guess does not have to be a possible secret. It only has to be a real word. A game that refusesghostfeels broken to the friend you hand it to — so the list a guess is checked against is deliberately much bigger than the list a secret is drawn from, which is why every function you write takes its list as an argument.
A shipped test, the_list_is_sound, has proved both lists sound since your
first cargo test — five lowercase letters per word, no word twice, every
secret also an accepted guess — measured from the lists themselves, not
promised in a comment.