Chapter 2 — Programming a Guessing Game
Hands-on tutorial building a number guessing game. Covers variables, input, external crates, and match expressions.
The Complete Game
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 Concepts
Variables and Mutability
#![allow(unused)] fn main() { let mut guess = String::new(); // mutable variable let secret_number = 42; // immutable by default }
- Variables are immutable by default
- Use
mutto make them mutable
Using External Crates
Add to Cargo.toml:
[dependencies]
rand = "0.8.5"
Import in code:
#![allow(unused)] fn main() { use rand::Rng; }
Reading User Input
#![allow(unused)] fn main() { io::stdin() .read_line(&mut guess) .expect("Failed to read line"); }
&mut guesspasses a mutable referenceexpect()handles potential errors
Type Conversion
#![allow(unused)] fn main() { let guess: u32 = guess.trim().parse() .expect("Please type a number!"); }
trim()removes whitespaceparse()converts string to number- Type annotation
: u32tells Rust what type to parse into
Match Expressions
#![allow(unused)] fn main() { match guess.cmp(&secret_number) { Ordering::Less => println!("Too small!"), Ordering::Greater => println!("Too big!"), Ordering::Equal => { println!("You win!"); break; } } }
matchis like switch but more powerful- Must handle all possible cases
- Can execute multiple statements in arms using
{}
Error Handling with Match
#![allow(unused)] fn main() { let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => continue, }; }
parse()returnsResult<T, E>(Ok or Err)- Match on the result to handle both cases
continueskips to next loop iteration on error
Takeaways
- Rust has no built-in randomness (use
randcrate) loopcreates infinite loops (usebreakto exit)- References (
&) let you access data without taking ownership Resulttype forces you to handle errors explicitly- Match expressions are exhaustive (must cover all cases)