Guessing Game (error handling)

Hi, there! I’m very new to Rust and am working through the No Starch Press book. I’ve just finished the chapter on error handling and realized that the updated code in it for the Guessing Game panics anytime a user makes a guess of over 100 (which, I guess was the point).

I searched and found a few questions on this forum about the guessing game, but they were about different versions. Apologies if there’s a dup I didn’t find!

I have a solution and would love feedback on it. Specifically, is there a good way to reuse the error string from Guess::new()?

extern crate rand;
use rand::Rng;
use std::cmp::Ordering;
use std::io;

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

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

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

    loop {
        println!("Please input your guess.");
        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess = match guess.trim().parse() {
            Ok(num) => match num > 100 {
                true => {
                    println!("Guess value must be between 1 and 100, got {}.", num);
                    continue;
                }
                false => Guess::new(num),
            },
            Err(_) => continue,
        };

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

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

pub struct Guess {
    value: u32,
}

impl Guess {
    pub fn new(value: u32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess { value }
    }

    pub fn value(&self) -> u32 {
        self.value
    }
}

You could return a Option<Guess> instead. If the numer is in range, e.g. between 0..100 return Some(Guess { value }) else None. And then you can check it in your main loop

1 Like

What do you mean by reusing? I think string literals always refer to the same String.

I think the best way to do this is to implement FromStr for Guess. That way you can call parse and get a Result<Guess, GuessParseError>.

1 Like

I just meant that I don’t want to have the same string inside the panic! for impl Guess and in the println! inside fn guessing_game().

I’m not sure how to follow your suggestion, but I’m sure it will help soon. I’ll keep going through the book and return here to re-read it in a couple of days.

I just meant that I don’t want to have the same string inside the panic! for impl Guess and in the println! inside fn guessing_game() .

I see now. I don’t think that is possible.

Here is an example of how you could handle invalid input (playground):

use core::str::FromStr;

#[derive(Eq, PartialEq, Debug)]
pub struct Guess {
    value: u32,
}

// FromStr for T provides `parse::<T>()`
impl FromStr for Guess {
    type Err = ParseGuessError;
    fn from_str(s: &str) -> Result<Guess, ParseGuessError> {
        if let Ok(val) = s.parse() {
            if val < 1 || val > 100 {
                Err(ParseGuessError::OutsideOfRange)
            } else {
                Ok(Guess { value: val })
            }
        } else {
            Err(ParseGuessError::NotANumber)
        }
    }
}

// You'll probably want to implement `Error` and include more information about
// the error to make it useful.
#[derive(Eq, PartialEq, Debug)]
pub enum ParseGuessError {
    NotANumber,
    OutsideOfRange
}

#[test]
fn test_parse() {
    assert_eq!("10".parse::<Guess>(), Ok(Guess {value: 10}));
    assert_eq!("1000".parse::<Guess>(), Err(ParseGuessError::OutsideOfRange));
    assert_eq!("ten".parse::<Guess>(), Err(ParseGuessError::NotANumber));
}