Printing number of try in random number game

Hi, I'm new here and at the Rust programming language. I've recently made the random number game of the book, and today I tried to upgrade it by printing the number of trys.
Here is my Cargo.toml:

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.5"
colored = "2.1.0"

And here is my src/main.rs:

use std::io;
use std::cmp::Ordering;
use rand::Rng;
use colored::*;

fn main() {
    println!("{}", "Take a Guess!".blue().bold());
    println!("{} {}", "Game programmed by".blue().bold(), "Oihan Deshayes".green().bold());


    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    loop {
        let mut try_number = 0;
 
        println!("{}", "Input your guess.".cyan().bold());
    

        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 input a valid number.".magenta());
                continue;
            }
        };

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("{}", "Too low!".red().bold()),
            Ordering::Greater => {
                try_number += 1;
                println!("{}", "Too high!".red().bold())
            },
            Ordering::Equal => {
                try_number += 1;
                println!("{}", "You win!".green().bold());
                println!("Game ended after {try_number} try(s).");
                break;
            }
        }
    }
}

When I run this, it keeps printing "Game ended after 1 try(s)."
It may look dumb but I'm really stuck. Please help.

There’s also a warning that should raise a suspicion

cargo check
warning: value assigned to `try_number` is never read
  --> src/main.rs:38:17
   |
38 |                 try_number += 1;
   |                 ^^^^^^^^^^
   |
   = help: maybe it is overwritten before being read?
   = note: `#[warn(unused_assignments)]` on by default

warning: `small_pg` (bin "small_pg") generated 1 warning

how come, the value is never read after being incremented in the Ordering::Greater branch?

The answer is scoping: Look closely at the code structure.

    loop {
        let mut try_number = 0;
        // …
        /* conditionally */ { try_number += 1; }
    }

At the beginning of each loop, you start out with a fresh, zero-initialized variable that only lives for the duration of a single loop iteration.

Simply re-order/re-structure it to

    let mut try_number = 0;
    loop {
        // …
        /* conditionally */ { try_number += 1; }
    }

to make progress :wink:

3 Likes

It works, thanks a lot! But I have another of my dumb newbie's question: why does it work? I don't really understand sorry.

Hmm… how best to answer this. Variable scope and initialization is hard to explain in few words. To simplify the explanation, maybe you have some prior knowledge we could build on? Are there other programming languages you are well familiar with, that we could try to build an analogous example in?

Actually, I've tried out a few programming languages, for example C++, but I'm not good at any of those.

Alright, the situation in C++ is similar/identical, but we’ll skip the analogies then :slight_smile:

So the thing about scopes… we have a variable declared inside of a block, like the body of a loop.

A block in Rust is a list of statements surrounded by { }, the thing following the word “loop” is a block, the whole body of a function is a block. Inside of blocks you can declare variables with let statements, and these variables go out of scope (and get destructed) at the end of the block they are declared in.

Ultimately, we want to understand the difference between these two functions.

// `some_condition` defined elsewhere

fn my_function1() {
    loop {
        let mut try_number = 0;
        try_number += 1;
        if some_condition() {
            println!("{try_number}");
            break;
        }
    }
}

fn my_function2() {
    let mut try_number = 0;
    loop {
        try_number += 1;
        if some_condition() {
            println!("{try_number}");
            break;
        }
    }
}

Beyond what’s written explicitly, it’s also useful to add in the implicit information of where try_number goes out of scope, and thus stops existing.

fn my_function1() {
    loop {
        let mut try_number = 0; // <- declared inside of the loop body
        try_number += 1;
        if some_condition() {
            println!("{try_number}");
            break;
        }
        // we get here at the end of each loop iteration

        // `try_number` goes out of scope here,
        // at the end of the loop body
    }
    // we get here after the `break`
}

fn my_function2() {
    let mut try_number = 0; // <- declared at the top level in the function body
    loop {
        try_number += 1;
        if some_condition() {
            println!("{try_number}");
            break;
        }
        // we get here at the end of each loop iteration
    }
    // we get here after the `break`

    // `try_number` goes out of scope here,
    // at the end of the function body
}

So in my_function1, at the end of each loop iteration, the variable try_number (then at value 1) stops existing. At the beginning of the next iteration, if break wasn’t reached, the let mut try_number = 0; introduces a new variable, with value 0, that is called try_number again, but logically distinct from the “try_number” of the previous iteration.

In my_function2, a variable called try_number is declared once for the whole execution of a my_function2 call. Throughout multiple loop iterations, it keeps the value it has, gets incremented potentially multiple times, and when println!("{try_number}"); is executed it can have a value other than 1, assuming the loop ran more than once before some_condition() became true. Only at the end of the my_function2 call, try_number goes out of scope, its memory is forgotten, and if the whole function my_function2 was called again, the variable would start out fresh with the value 0 again.

4 Likes

ok, thanks a lot for the answers, I understand a lot better now! However, I now encounter another problem. Here's my code:

use std::io;
use std::cmp::Ordering;
use rand::Rng;
use colored::*;

fn main() {
    println!("{}", "Take a Guess!".blue().bold());
    println!("{} {}", "Game programmed by".blue().bold(), "Oihan Deshayes".green().bold());


    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    let mut try_number = 0;        

    loop {
 
        println!("{}", "Input your guess.".cyan().bold());
    
        try_number += try_number;

        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 input a valid number.".magenta());
                continue;
            }
        };

        match guess.cmp(&secret_number) {
            Ordering::Less => println!{
                try_number += 1;
                ("{}", "Too low!".red().bold());
            },
            Ordering::Greater => {
                try_number += 1;
                println!("{}", "Too high!".red().bold());
            },
            Ordering::Equal => {
                try_number += 1;
                println!("{}", "You win!".green().bold());
                println!("Game ended after {try_number} try(s).");
                break;
            }
        }
    }
}

When I cargo run, I get those errors:

error: expected `,`, found `;`
  --> src/main.rs:39:32
   |
39 |                 try_number += 1;
   |                                ^ expected `,`

error: expected one of `,`, `.`, `?`, or an operator, found `;`
  --> src/main.rs:40:48
   |
40 |                 ("{}", "Too low!".red().bold());
   |                                                ^ expected one of `,`, `.`, `?`, or an operator

error: format argument must be a string literal
  --> src/main.rs:39:17
   |
39 |                 try_number += 1;
   |                 ^^^^^^^^^^^^^^^
   |
help: you might be missing a string literal to format with
   |
39 |                 "{} {}", try_number += 1;
   |                 ++++++++

error: could not compile `guessing_game` (bin "guessing_game") due to 3 previous errors

You’ve made a syntactical error. Those can be a bit hard to debug, because wrong syntax can throw the compiler off, and make it report on places that aren’t exactly the place where something needs to be fixed.

It will become easier to solve an issue like this when you get more familiar with Rust syntax, so you could notice the problem by just looking at your code again. Well, let’s practice this anyways, and instead of me giving away the answer immediately, you can try to notice the problem yourself. Spotting and fixing syntax errors is a very good exercise when learning Rust.

Maybe all you need is the following hint:

Try to ignore the specific error messages, and play spot-the-difference, to see in what way your match arm with a syntax error

Ordering::Less => println!{
    try_number += 1;
    ("{}", "Too low!".red().bold());
},

differs from the one after it without syntax errors

Ordering::Greater => {
    try_number += 1;
    println!("{}", "Too high!".red().bold());
},
3 Likes

Ok, I have fixed it, no more of those really dumb problems :sweat_smile:. Thanks a lot for all the answers, it really helped me a lot!

There are no dumb problems as long as you learn something ^^

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.