DEV Community

Cover image for The Rust Journey of a JavaScript Developer • Day 2
Federico Moretti
Federico Moretti

Posted on

The Rust Journey of a JavaScript Developer • Day 2

It’s time for the second episode of this series. If you missed the first one, you can always go back and catch up: today we will program a guessing game together. This chapter of The Rust Programming Language is 100% dedicated to it, so I’m going to share my solution and impressions about the first hands-on project in Rust.


If you remember, we built a hello_world project using Cargo: the official Rust package manager. We are going to use those commands to create a guessing_game which will have the same structure at first. In fact, executing cargo new will always produce a generic template with a Hello, world! terminal output.

Programming a Guessing Game

Here the goal is to create a game that generates a random integer between 1 and 100, and the player will be asked to guess it. If the answer is right, the program will print a congratulation message and exit, otherwise it will indicate if the guessed number is either too low or too high. Nothing that complicated.

cd ~/projects/
cargo new guessing_game
cd guessing_game/
Enter fullscreen mode Exit fullscreen mode

As I mentioned, Cargo will generate a boilerplate: if you try to run it, it will print Hello, world! in the terminal, because it’s identical to the first one we built earlier. Except for the folder and the project name in Cargo.toml, guessing_game is an exact copy of hello_world. Then, we will make lot of changes to these files.

Processing a Guess

We will see that Rust make use of terms that could be more familiar for those who already know general-purpose programming languages, rather than JavaScript on its own, but if you have ever had a computer science class, you shouldn’t have any problem. It’s just a matter of thinking at a higher level.

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}");
}
Enter fullscreen mode Exit fullscreen mode

We do have equivalent in JavaScript, of course. Above, we can see something new worth noting: for example, std::io that in Rust is called “prelude” and refers to the language standard library. Do you remember? We must use 4-spaces indentation like in Python. Then, I can notice another couple of things here.

First, the let mut declaration: Rust distinguishes between immutable and mutable variables this way. Without appending mut, we would have had an immutable variable; so, an expression {guess} delimited by curly braces which will be replaced with the user input.

Talking about the user input itself, Rust uses read_line which is really close to readline in Node.js. I forgot to say that you must replace the default main.rs content with the code block above: it seems that the function accept every string, no matter its type, then you can enter anything when prompted.

Guess the number!
Please input your guess.
5
You guessed: 5
Enter fullscreen mode Exit fullscreen mode

The main() function is now only a sort of “echo” that prints whatever you enter in the terminal—the number 5, in this case. But we need to generate a random number to compare with the input: here comes our first dependency to add in Cargo.toml. It’s curious that an external library is needed.

Generate a Random Number

The most important part of the game is generating the random number, which implies to set a defined range. Then, we will compare it with the input in a configurable loop of attempts. Despite its simplicity, I’m learning a lot from it about Rust and its structure.

[dependencies]
rand = "0.9.2"
Enter fullscreen mode Exit fullscreen mode

I decided to use the latest version available of rand, but you can update it later. Running cargo build will resolve dependencies and recompile the sources: let’s see how to actually generate a random number. Things would have been easier with JavaScript, while Rust provides something closer to Python.

use std::io;

use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::rng().random_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");

    println!("You guessed: {guess}");
}
Enter fullscreen mode Exit fullscreen mode

Above, secret_number stores an immutable integer between 1 and 100. The book will share more details on rand::Rng later, but you may notice that random_range accepts two values: a start and an end along with a spread syntax made of two dots. That’s how we specify the range.

Comparing the Guess to the Secret Number

If you’re using VS Code and you installed the extension I suggested, you’re now able to run and/or debug the main() function with a single click. We still have to compare the two numbers, then yet another library is required to show hints if the guessing is wrong.

use std::cmp::Ordering;
use std::io;

use rand::Rng;

fn main() {
    // --snip--

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}
Enter fullscreen mode Exit fullscreen mode

Before saying anything about the new lines, notice that some code has been replaced by a comment that starts with // just like it does in JavaScript. Don’t confuse it with inline IDE suggestions which on VS Code appear of the same color—if you have a dedicated extension.

Allowing Multiple Guesses with Looping

We’re almost done, since we only have to add a loop that keeps answering for a number until the player guess the generated number. Here the approach is pretty different, compared to JavaScript: Rust uses a sort of hybrid between a while loop and a switch statement.

// --snip--

println!("Please input your guess.");

    loop {
        // --snip--

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The whole guessing logic, from variable declaration to the end, is now a loop which checks the entered number and compares it with the generated one. It will go on asking and hinting until you guess, but it will return an unhandled error if the player enters a string instead of a number.

        // --snip--

        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}");

        // --snip--
Enter fullscreen mode Exit fullscreen mode

By removing .expect() and setting a match, we can leave an empty line, waiting for a number to be entered. This is a TDD approach I really enjoy: for sure our error handling can be better, but the game is finally complete and doesn’t break in case of input strings.


If you had a look at the original book chapter, you may have noticed that I changed a bit the random number generation mechanism: Rust deprecated both thread_rng and gen_range, so I thought it would have been better to include the new methods instead. If you’re using an older version, there’s the chance you must rollback them.

Top comments (0)