Custom Type for Validation in Guessing Game


#1

I write the code according to Chapter 2 “Guessing Game Tutorial”.
I then refactored it to use a Custom Type for Validation after reading [https://github.com/rust-lang/book/blob/master/2018-edition/src/ch09-03-to-panic-or-not-to-panic.md](Chapter 9 “To panic! or Not to panic!”).
I wrote unit tests to accompany the code.
I’ve pushed a commit to my Github repo here: https://github.com/ltfschoen/RustTest/blob/251880ced38a9ec42f799d8005a7eddfd5811f2e/projects/guessing_game/src/main.rs.
All the tests pass when I run cargo test.
And when I compile and run the code with cargo run outputs the following.
It uses the Custom Type for Validation correctly and displays messages as expected for when I enter a value that is both between the range 0 and 100 and either less than the secret number or greater than the secret number. It panics and displays a message as expected when the user enters a positive value outside the range (i.e. 101 or 0). It exits the program when I enter a guess that matches the secret number as expected.
However, when I enter a negative number (i.e. -1), it triggers an error in the match operator on Line 67 in my code, which triggers continue that skips to the next iteration of the loop that just asks for the user to enter another guess, it doesn’t reach Line 14 of the Custom Type for Validation.
I’m using Visual Studio Code with breakpoints but I still do not understand why.

The secret number is: 83
Input your guess.
82
You guessed: Guess { value: 82 }
Too small!
Input your guess.
84
You guessed: Guess { value: 84 }
Too big!
Input your guess.
83
You guessed: Guess { value: 83 }
You win!

 Ls /Users/Ls/code/clones/RustTest/projects/guessing_game [git:master]
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/guessing_game`
The secret number is: 54
Input your guess.
100
You guessed: Guess { value: 100 }
Too big!
Input your guess.
101
thread 'main' panicked at 'Guess value must be between 1 and 100, got 101.', src/main.rs:16:13
note: Run with `RUST_BACKTRACE=1` for a backtrace.

 Ls /Users/Ls/code/clones/RustTest/projects/guessing_game [git:master]
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/guessing_game`
The secret number is: 48
Input your guess.
-1
Input your guess.

I tried to replicate the scenario at https://play.rust-lang.org/ with the following code, but it matches the arm Ok instead of Err and outputs Success: -1, which differs from what’s happening in my Github repo Rust script.

let mut guess: String = "-1".to_string();
let guess: String = match guess.trim().parse() {
    Ok(num) => { println!("Success: {}", num); num }, // Success: -1
    Err(err) => { println!("Error: {}", err); err.to_string() },
};

Note: If you want to run my script after cloning my repo, run the following:
cd projects/guessing_game;
cargo run;
cargo test;

Initially I didn’t understand why entering a negative value for the user input guess of -1 wasn’t able to triggered the Ok match arm and ran the Custom Type Validator where it would panic, and instead ran Err arm and restarted the loop of asking for a guess from the user…

But after writing this post I made some changes and got it to work successfully with the Custom Type for Validations and panic for negative input values, which I’ve published in this commit

$ cargo run
   Compiling guessing_game v0.1.0 (file:///Users/Ls/code/clones/RustTest/projects/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.65 secs
     Running `target/debug/guessing_game`
The secret number is: 31
Input your guess.
-1
thread 'main' panicked at 'Guess value must be between 1 and 100, got -1.', src/main.rs:16:13
note: Run with `RUST_BACKTRACE=1` for a backtrace.

But why can’t I change secret_number to be a u32 instead of i32?
i.e. on Line 26 why doesn’t it work when I change it to
pub fn comparison(&self, secret_number: &u32) -> i32 {
and on Line 100 why doesn’t it work when I change it to:
let actual_secret_number: u32 = 50;

It gives error message: expected i32, found u32


#2

I am also learning Rust, so I am not 100% sure. Please forgive any mistakes!

The Err arm ran because the parse() function is failing to convert a negative value into a u32. if you want to match the Ok arm, you have to use:

 // Allow potentially negative numbers
 let guess: Guess = match guess.trim().parse::<i32>() {
    Ok(num) => Guess::new(num), // you have to change every `u32` type to `i32` in `Guess` struct.
    Err(_) => continue,
};

I don’t get this. Are you wanting to have a negative secret_number? Care to elaborate more?

Thanks!


#3

Ok thanks. I got my implementation working so given a guess of -1 it runs the Ok arm and calls the Custom Type for Validation and panics as expected.

But the unit tests are not working for some reason.

Sorry for the confusion. I meant to say the secret_number should only be a positive number >=0, so I want it to be u32 (unsigned, so only positive).

Whereas the guesses from the user can be positive or negative values i32 (signed, so both positive or negative).

I’ve fixed my original post to reflect your feedback as I had explained it the wrong way around.

I made the following changes to the code:

But when I run my unit tests it gives the following error:

35%20am

But the reason I tried to cast it back from u32 unsigned to i32 signed is because the Rust source code for cmp appears to expect it to be i32 since they cast to i32 both values that are being compared as shown on Line 490 in the Rust libcore source code here, but when i’m comparing only the self.value which represents the current guess is i32 type, whereas the secrete_number is u32 as previously mentioned.


#4

To get around the cast issue:

match self.value.cmp(&(*secret_number as i32)) {
...
}

#5

Thanks. I tried that but I still get the same error when I run the tests. I’ve pushed the code with your changes in this branch https://github.com/ltfschoen/RustTest/compare/bug_casting


#6

You pushed

match self.value.cmp(&(*secret_number as &i32)) {

but notice how the snippet I gave had as i32, not as &i32 :slight_smile:


#7

Perfect! that solved it, thanks!