You'll learn: how a program picks between two things — if and else, and
the true/false values they run on. You will also meet Rust's two kinds of text,
&str and String, and clean up the mess a player types. At the end you type a
word and your game answers back.
What we are building
Last lab the game spoke. This lab it listens. One guess, answered:
Type a guess and press enter:
crane
You got it!
The middle line is the player's own typing, echoed back by the terminal.
Four small functions sit between the typing and the answer. Each does a single
job, in order: tidy what was typed, check it is the right length, check whether
it is the secret word, and say so. Reading the keyboard stays in main.rs —
the game's shell, as lab 01 called it — ungraded, exactly like last lab's
printing.
Yes-or-no values: bool
Rust has a type for the answer to a yes-or-no question:
bool— a value that is eithertrueorfalse, and nothing else.==— asks are these the same?, and hands back abool.!=— the opposite one, are these different?.a != band!(a == b)say the same thing; the first is shorter and the second is built out of pieces you already have. Either is fine wherever you meet it.!— not: flips abool, so!wonistrueexactly whenwonisfalse.
typed == "crane" // true when what was typed is exactly this word
typed.len() == 5 // true when its length is exactly five
You rarely type true or false yourself; you get them by asking questions.
Note the doubling. == asks a question; a single = gives a name a value, the
way let word_len = 5; did in lab 01. Mixing them up is a classic first-week
slip, and the compiler catches it every time.
A function can return a bool like any other value, and when it does, its name
should read like the question it answers.
Making a choice: if and else
if won {
// runs when `won` is true
} else {
// runs when it is false
}
if takes a bool and runs one of two branches — the code in the braces
after if, or the code in the braces after else. That is the whole tool. The
braces are always required, even for one line.
if/else can also be a value, the same way a function body's last line is
its return value. Each branch ends with a bare, semicolon-free line, and
whichever branch runs hands its value out:
let mood = if won { "delighted" } else { "determined" };
This lab's answer_line is exactly that shape: one choice, two possible
strings, and the chosen one is the function's return value — with no return
keyword anywhere, because the branch that runs is already the function's last
value.
Two kinds of text: &str and String
You have now seen both of Rust's everyday text types, so here is the difference, once, in plain words.
&str— text you are looking at. Quoted literals like"crane"have this type, and so does text a function borrows to read.String— text you own and can build: whatformat!returns, whatpush_strgrows, what this lab's cleanup function hands back.
The & on &str is the one you typed on trust in lab 01, in
push_str(&rules_line(…)). It means a view of text that lives somewhere else:
the owner lends it, the reader borrows it, and the reader may look but does not
own.
The working rule for this course: where a function takes text it takes &str
(it only needs to look), and where it hands text back it hands back String (a
new piece of text for you to keep). Both of this lab's text-returning functions
follow it, and so does every one after.
When the compiler says expected `String`, found `&str` , it means a
function promised to hand over owned text and tried to hand over a view
instead. The project walks you into that error on purpose, so you meet it with
the explanation still fresh.
Cleaning input: text comes in messy
When a player types CRANE and presses enter, your program receives the
spaces, the capitals, and the enter key itself — a newline character on the
end. Compare that raw mess against "crane" and you get false for a guess
that deserved true.
You could scrub the text everywhere you use it. Do not. The craft rule this lab teaches is clean once, at the door: one function whose whole job is tidying, called the moment input arrives, so every function after it can assume clean text.
Rust's &str comes with the two scrubbing tools built in, both leaving the
original alone:
" CRANE \n".trim() // "CRANE" — whitespace gone from both ends
"CRANE".to_lowercase() // "crane"
.trim() removes whitespace — spaces, tabs, and that trailing newline —
from both ends only; spaces in the middle of text are untouched. It hands back
a &str, a view of what is left between the trimmed ends; .to_lowercase()
has to build a new String, because different letters are different text. And
because each returns text, they chain:
" CRANE \n".trim().to_lowercase() // "crane", in one go
A chain reads left to right — trim, then lowercase — and this lab's first function is one chain on the text it is handed.
Counting letters: .len()
guess.len() answers how long a piece of text is, and this lab uses it to
refuse a four-letter guess at a five-letter word. One caveat, because it
matters later: .len() counts bytes, not letters — in text with accents or
emoji, one letter can take several bytes. Every word in this game is plain
lowercase a–z (the word list's own shipped test proves it), and those are
one byte each, so for the words this game knows, the byte count is the letter
count.