Building a Guessing Game
Now that we’ve covered the basics, let’s build a simple guessing game to get a better feel for Rust. We’ll build the same application in both Rust and JavaScript to highlight the differences and similarities.
Project Home
We’ll create a number guessing game where:
- The program generates a random number between 1 and 100
- The player inputs guesses
- The program provides feedback on each guess
- The game continues until the player guesses correctly
Setting Up the Rust Project
Let’s start by creating a new Rust project:
cargo new guessing_gamecd guessing_game
The Initial Rust Program
Open src/main.rs
and replace its contents with:
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);}
JavaScript Equivalent
For comparison, here’s how we’d start in JavaScript:
const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout});
console.log("Guess the number!");
console.log("Please input your guess:");
readline.question('', (guess) => { console.log(`You guessed: ${guess}`); readline.close();});
Understanding the Rust Code
Let’s break down the Rust code:
use std::io;
- Imports the I/O library (similar torequire
in Node.js)let mut guess = String::new();
- Creates a mutable variable (notemut
)io::stdin().read_line(&mut guess)
- Gets user input and stores it inguess
.expect("Failed to read line")
- Handles potential errors (we’ll explore better error handling later)
Key differences from JavaScript:
- Variables are immutable by default in Rust; we need
mut
to make them mutable - In Rust, we handle errors explicitly with
.expect()
or other error handling methods
Adding Random Number Generation
Let’s update our program to generate a random number. First, we need to add the rand
crate to our dependencies.
Edit Cargo.toml
:
[dependencies]rand = "0.8.5"
Now, 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); // For debugging
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!"), }}
JavaScript Equivalent
const readline = require('readline').createInterface({ input: process.stdin, output: process.stdout});
console.log("Guess the number!");
const secretNumber = Math.floor(Math.random() * 100) + 1;
console.log(`The secret number is: ${secretNumber}`); // For debugging
console.log("Please input your guess:");
readline.question('', (guessInput) => { const guess = Number(guessInput.trim());
console.log(`You guessed: ${guess}`);
if (guess < secretNumber) { console.log("Too small!"); } else if (guess > secretNumber) { console.log("Too big!"); } else { console.log("You win!"); }
readline.close();});
Key Differences So Far
-
Error Handling:
- Rust: Explicit with
.expect()
or other methods - JavaScript: Often implicit or with try/catch blocks
- Rust: Explicit with
-
Type Conversion:
- Rust: Explicit parsing with
.parse()
to convert strings to numbers - JavaScript: Implicit conversion or explicit with
Number()
- Rust: Explicit parsing with
-
Comparison:
- Rust: Uses pattern matching with
match
expression - JavaScript: Uses if/else statements
- Rust: Uses pattern matching with
Adding a Loop for Multiple Guesses
Let’s update our Rust program to allow multiple guesses:
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(_) => { println!("Please type a number!"); 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; } } }}
JavaScript Equivalent
const readline = require('readline');
function guessTheNumber() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
console.log("Guess the number!");
const secretNumber = Math.floor(Math.random() * 100) + 1;
const askGuess = () => { rl.question('Please input your guess: ', (guessInput) => { const guess = Number(guessInput.trim());
if (isNaN(guess)) { console.log("Please type a number!"); askGuess(); return; }
console.log(`You guessed: ${guess}`);
if (guess < secretNumber) { console.log("Too small!"); askGuess(); } else if (guess > secretNumber) { console.log("Too big!"); askGuess(); } else { console.log("You win!"); rl.close(); } }); };
askGuess();}
guessTheNumber();
Improved Error Handling
Notice how our Rust code now uses pattern matching to handle potential errors when parsing the input:
let guess: u32 = match guess.trim().parse() { Ok(num) => num, Err(_) => { println!("Please type a number!"); continue; }};
This is using Rust’s Result
type, which we’ll explore more in the error handling section.
Running the Complete Game
Run the Rust version:
cargo run
Comparing the Full Implementations
Let’s highlight the key differences between our Rust and JavaScript implementations:
-
Memory Safety:
- Rust guarantees memory safety at compile time
- JavaScript relies on runtime checks and garbage collection
-
Error Handling:
- Rust uses Result types that force us to handle errors explicitly
- JavaScript typically uses try/catch blocks or callbacks for error handling
-
Type System:
- Rust is statically typed, requiring explicit type annotations
- JavaScript is dynamically typed with optional type systems like TypeScript
-
Concurrency Model:
- Rust’s ownership system prevents data races at compile time
- JavaScript uses an event loop with a single thread by default
-
Performance:
- Rust compiles to native code with performance similar to C/C++
- JavaScript depends on the runtime’s JIT compiler
Next Steps
You’ve now successfully built a complete, interactive guessing game using Rust’s language features. Next, we’ll cover Common Programming Concepts in more detail, starting with variables and mutability.