Programming a Guessing Game
Our first project will be to implement a guessing game from scratch. The game should work like this:
- The program will greet the player.
- It will generate a random integer between 1 and 100.
- It will then prompt the player to enter a guess.
- After a guess is entered, the program will indicate whether the guess is too low or too high.
- If the guess is correct, the game will print a congratulatory message and exit.
- Otherwise, the game keeps going.
Setting Up a New Project
First, setup a new project:
$ cargo new guessing_game
$ cd guessing_game
What does this do?
The first command, cargo new
, takes the name of the project (guessing_game
) as the first argument. The second command changes to the new project’s directory.
Then run the project:
$ cargo run
It should print Hello world!
.
Why does it print this?
This is the default behavior for new binaries. Look at src/main.rs
.
Processing a Guess
Open src/main.rs
and look at the main
function. Right now, it contains the one line:
#![allow(unused)] fn main() { println!("Hello, world!"); }
$ cargo run
Guess the number!
Please input your guess.
For this first project, we will check your work with a provided testing binary called guessing-game-grader
. Before making your changes, try running it to observe the failing test:
$ guessing-game-grader
✗ Task 1: Prints the right initial strings
The diff is:
H̶e̶l̶l̶o̶,̶ ̶w̶o̶r̶l̶d̶!̶
Guess the number!
Please input your guess.
Once you make the correct edit, the grader should show the test as passing and move on to the next test, like this:
$ guessing-game-grader
✓ Task 1: Prints the right initial strings
✗ Task 2: Accepts an input
[...]
I need a hint!
TODO
I need the solution!
TODO
Once you're ready, move on to the next task.
$ cargo run
Guess the number!
Please input your guess.
15
You guessed: 15
The key method is Stdin::read_line
. A core skill in the Rust It Yourself series will be learning to read documentation of unfamiliar methods, so let's read the documentation for read_line
:
How was I supposed to know that Stdin::read_line
is the key method?
TODO (Google, StackOverflow, ChatGPT, top-down search of the docs, ...)
#![allow(unused)] fn main() { pub fn read_line(&self, buf: &mut String) -> Result<usize> }
Locks this handle and reads a line of input, appending it to the specified buffer. For detailed semantics of this method, see the documentation on
BufRead::read_line
.Examples
#![allow(unused)] fn main() { use std::io; let mut input = String::new(); match io::stdin().read_line(&mut input) { Ok(n) => { println!("{n} bytes read"); println!("{input}"); } Err(error) => println!("error: {error}"), } }
The first line is the type signature of read_line
. It says: "I am a method that takes two arguments: an immutable reference to myself (Stdin
), and a mutable reference to a String
. I return a type called Result<usize>
."
This type signature and example use Rust features we haven't discussed yet. That's expected — another core skill in the Rust It Yourself series is working with code that uses features you don't fully understand. So let's try and learn something from this example anyway.
The read_line
method demonstrates two key aspects of Rust:
-
Mutability: Rust requires you to be more explicit about when data is mutated in-place. Here, calling
read_line
mutatesinput
in-place. That is represented by the fact that the second argument is not a plainString
, but instead a mutable reference&mut String
. Additionally, the variableinput
must be declared asmut
so we are allowed to mutate it. -
Error handling: Rust does not have a concept of
undefined
(as in JS) orNone
(as in Python) orNULL
(as in C++) ornil
(as in Go). Rust also does not have exceptions. Instead, to represent "this operation can succeed and returnX
or fail and returnY
", Rust uses enums in combination with pattern matching via operators likematch
. Unlike enums in other languages, Rust's enums can have fields (similar to tagged unions in C).
Now, try copy-pasting this example into the bottom of your main
function. Run the code (with cargo run
), see how it works, and try editing the example so it completes Task 2.