The guide will walk you through creating a simple but complete number guessing game in Rust. By the end, you'll have built an interactive console application that:
- Generates a random number
- Takes user input
- Compares the guess to the secret number
- Provides feedback
- Allows multiple guesses until the correct answer is found
Along the way, you'll learn key Rust concepts that form the foundation of the language.
Prerequisites
- Rust installed on your system (see our installation guide)
- Basic familiarity with command-line interfaces
- A text editor of your choice
Step 1: Set Up a New Rust Project
First, let's create a new Rust project using Cargo:
$ cargo new guessing_game
$ cd guessing_gameThis creates a new directory called guessing_game with the following structure:
-
Cargo.toml: The project configuration file -
src/main.rs: The source code file with a "Hello, world!" starter program
Let's take a quick look at the generated files:
Cargo.toml:
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
[dependencies]src/main.rs:
fn main() {
println!("Hello, world!");
}You can already compile and run this program with:
$ cargo runStep 2: Set Up the Basic Game Structure
Let's modify src/main.rs to create the initial structure of our game:
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}Let's break down what's happening here:
Understanding Each Part
use std::io;: This imports the input/output functionality from Rust's standard library.println!("Guess the number!");: Theprintln!macro prints text to the screen.-
let mut guess = String::new();: This line creates a new variable calledguess.-
letdeclares a new variable -
mutmakes the variable mutable (able to change its value) -
String::new()creates a new, empty string - Without
mut, Rust variables are immutable by default!
-
-
io::stdin().read_line(&mut guess): This reads a line of input from the user.-
stdin()returns a handle to the standard input -
read_line()reads user input into the string we provided -
&mut guesspasses a mutable reference to ourguessvariable - The
&indicates this is a reference, allowing multiple parts of code to access the same data
-
-
.expect("Failed to read line");: Handles potential errors fromread_line().-
read_line()returns aResulttype, which can be eitherOkorErr -
expect()will crash the program with the given message if an error occurs - If successful,
expect()returns the value insideOk
-
-
println!("You guessed: {}", guess);: Prints out what the user guessed.- The
{}is a placeholder where the value ofguesswill be inserted
- The
Test this code by running:
$ cargo runStep 3: Generate a Random Number
To make our game functional, we need to generate a random number. Rust's standard library doesn't include random number functionality, so we'll use the rand crate (a Rust package).
First, update Cargo.toml to include the rand dependency:
[dependencies]
rand = "0.8.5"Now, modify src/main.rs to use this crate:
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {}", secret_number); // We'll remove this later
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}Understanding the New Code
use rand::Rng;: Imports theRngtrait, which defines methods that random number generators implement.-
let secret_number = rand::thread_rng().gen_range(1..=100);:-
thread_rng()gives us the random number generator for the current thread -
gen_range(1..=100)generates a random number in the range 1 to 100 (inclusive) - The
1..=100syntax creates an inclusive range
-
When you run cargo build for the first time after adding a dependency, Cargo downloads all necessary crates from the registry (crates.io). This happens only once unless you update the dependency.
Step 4: Compare the Guess with the Secret Number
Now we need to compare the user's guess with our secret number. We'll need to:
- Convert the user's input (a string) to a number
- Compare the two numbers
- Tell the user if they're too high, too low, or correct
Update src/main.rs:
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {}", secret_number);
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}Understanding the New Code
use std::cmp::Ordering;: Imports theOrderingenum, which has variantsLess,Greater, andEqual.-
let guess: u32 = guess.trim().parse().expect("Please type a number!");:- This creates a new variable called
guessthat shadows (reuses) the name of the previousguessvariable -
guess.trim()removes whitespace at the beginning and end, including the newline character -
.parse()tries to convert the string to another type (in this case, a number) -
let guess: u32tells Rust that we wantguessto be an unsigned 32-bit integer -
expect()handles any errors from parsing
- This creates a new variable called
-
match guess.cmp(&secret_number) { ... }:-
guess.cmp(&secret_number)compares the two values and returns anOrdering -
matchworks like a sophisticated switch statement that handles all possible cases - Each arm of the
matchhas a pattern and code to run if the pattern matches
-
Step 5: Allow Multiple Guesses with Looping
Let's improve our game by allowing the player to guess until they get it right, using Rust's loop keyword:
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {}", secret_number);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}Understanding the Loop
loop { ... }: Creates an infinite loop that will keep asking for guesses.break;: Exits the loop when the user guesses correctly.
Step 6: Handle Invalid Input
Our game currently crashes if the user enters something that's not a number. Let's handle this gracefully:
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}Understanding Error Handling
-
match guess.trim().parse() { ... }:- Instead of using
expect(), we're now usingmatchto handle theResultreturned byparse() - If parsing succeeds,
parse()returnsOk(num)wherenumis the parsed number - If parsing fails,
parse()returns anErrvalue
- Instead of using
Ok(num) => num,: If parsing succeeded, extract and use the number.-
Err(_) => continue,: If parsing failed, ignore the error and start the next loop iteration.- The underscore
_is a catchall pattern that matches any error -
continueskips the rest of the current loop iteration and starts the next one
- The underscore
Step 7: Final Polishing - Remove Debug Output
Now that our game is complete, let's remove the line that prints the secret number:
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}Key Rust Concepts You've Learned
-
Variables and Mutability
- Variables are immutable by default
- Use
mutto make variables mutable
-
Data Types
- Explicit type annotations (
let guess: u32) - Type conversion (string to number)
- Explicit type annotations (
-
Functions and Macros
- Function calls (e.g.,
trim(),parse()) - Macros (e.g.,
println!)
- Function calls (e.g.,
-
Control Flow
-
matchexpressions for pattern matching -
loopfor creating infinite loops -
breakto exit loops -
continueto skip to the next iteration
-
-
Error Handling
-
Resulttype withOkandErrvariants - Using
expect()to handle errors simply - Pattern matching on
Resultfor more nuanced error handling
-
-
External Crates
- Adding dependencies to
Cargo.toml - Importing functionality with
use
- Adding dependencies to
-
Ownership and References
- Using
&to pass references - Mutable references (
&mut)
- Using
Conclusion
Congratulations! You've built a complete Rust program from scratch. This guessing game demonstrates many of Rust's core concepts in a practical way. Here's what your program can now do:
- Generate a random number between 1 and 100
- Accept user input as a guess
- Compare the guess to the secret number
- Give feedback (too small, too big, or you win)
- Allow multiple guesses until the correct answer is found
- Handle invalid input gracefully
As you continue your Rust journey, you'll explore these concepts in more depth and learn about Rust's powerful features like ownership, traits, and concurrency.
Next Steps
To further enhance your game, consider these challenges:
- Add a counter to track how many guesses the player needed
- Add difficulty levels with different number ranges
- Limit the number of guesses the player can make
- Add a timer to measure how long it takes to guess correctly
Happy Rust coding.