Rust for the Novice Programmer/Basic Maths Testing Program/Source Code and Extensions

Full Source Code edit

/// bind fmt and io to std::fmt and std::io to shorten later
use std::fmt;
use std::io;

/// Entry point of our program
fn main() {
    //generate and print a random maths question
    let question = MathsQuestion::new_random();
    question.print();
    let val = wait_for_input();
    let result = question.calc_result();
    if val == result {
        println!("You got it correct! Well done!");
    } else {
        println!("Incorrect! The correct answer was {}", result);
    }
}
/// Gets input from stdin (standard input)
/// Will ask continuously until a valid i32 number is inputted
fn wait_for_input() -> i32 {
    //create a new string buffer
    let mut buffer = String::new();
    //reads a line from stdin into the string buffer
    io::stdin().read_line(&mut buffer).unwrap();
    //remove newline characters from the string
    buffer = buffer.replace("\r", "").replace("\n", "");
    //attempts to convert the string into a base 10 i32 number
    let mut result = i32::from_str_radix(&buffer, 10);
    //as long as converting the string to a number fails, keep asking for new input from the user
    //the underscore indicates we are ignoring the specific error since we don't require it for anything
    while let Err(_) = result {
        println!("Invalid number, please re-enter:");
        //clear the string buffer so it doesn't contain last line's result
        buffer.clear();
        //reads a line from stdin again
        io::stdin().read_line(&mut buffer).unwrap();
        //remove newline characters again
        buffer = buffer.replace("\r", "").replace("\n", "");
        //try to convert the string into an i32 number again
        result = i32::from_str_radix(&buffer, 10);
    }
    //result must be an Ok() value here, so this unwrap is safe
    let num = result.unwrap();
    //return the number
    num
}
/// An enum representing an operator in a maths equation
enum Operator {
    Addition,
    Subtraction,
}
/// Implement the Display trait on the Operator type, allowing it to be displayed using {} in format strings
impl fmt::Display for Operator {
    //the required 
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Operator::Addition => write!(f, "+"),
            Operator::Subtraction => write!(f, "-"),
        }
    }
}
/// A struct representing a maths question
struct MathsQuestion {
    left: i32,
    right: i32,
    operator: Operator,
}
impl MathsQuestion {
    /// Construct a new MathsQuestion from parts
    fn new(left: i32, right: i32, operator: Operator) -> MathsQuestion {
        MathsQuestion {
            left,
            right,
            operator,
        }
    }
   
    /// Creates a new MathsQuestion from random values
    fn new_random() -> MathsQuestion {
        //random values between -50 and +50 (inclusive)
        let left = fastrand::i32(-50..=50);
        let right = fastrand::i32(-50..=50);
        //operator randomly addition or subtraction (50/50)
        let operator = if fastrand::bool() {
            Operator::Addition
        } else {
            Operator::Subtraction
        };
        MathsQuestion::new(left, right, operator)
    }
   
    /// Prints out the Maths question
    fn print(&self) {
        println!("What is {} {} {}?", self.left, self.operator, self.right);
    }
   
    /// Calculates the expected result of the maths question
    fn calc_result(&self) -> i32 {
        match self.operator {
            Operator::Addition => self.left + self.right,
            Operator::Subtraction => self.left - self.right,
        }
    }
}

Possible extensions you could try to implement, in order from easy to hard with some hints:

  1. Ask multiple questions in one run of the program(maybe use a for loop to run the code inside the main function a certain number of times)
  2. When asking multiple questions, add a counter that tracks how many of the questions the user got correct and print out what percentage they got correct
  3. Add multiplication and division to the operators(note you will need to change all the spots the operator is used) also you will need to change the number type for division if you want to handle that, although this may have more problems since what do you do about 1 divided by 3 since the resulting decimal will be 0.3333333333 which the user can't be expected to put in. Also make sure you handle division by 0 since that would cause problems. There are a lot of things to consider so this may be quite difficult if you want 'proper' division but integer division is an easier thing to implement.
  4. Track the time it takes the user to answer each question. It is suggested you use the standard library Instant struct to track the time. You can then print out the mean, median or other statistical measures. It is also suggested to use a Vec to store the times.
  5. Create more complicated maths questions with random numbers of operators in a single question. This is quite complicated for reasons that may be quite obscure with the Rust knowledge you have now but will be elaborated later. You may also want to put brackets between formulas

Please don't feel obligated to do these, only do them if you think it's interesting and you're having fun. You may easily run into issues that you don't know how to solve because of limited knowledge that will be covered later. The best advice is to read and try to understand compiler errors, maybe searching them if you're not sure for more context. Move on when you've had enough.

Next: