Activity 10 - Make a Calculator with Error Handling

Goal: Build a calculator that uses Result<T, E> for error handling and Option<T> for operations.

Instructions

  1. Go to https://classroom.github.com/a/PJAfqzH5 to accept the assignment in github classroom. You can work together in small groups on a single repo or work on your own repo even if you are discussing with others - your choice!
  2. Complete the code in src/main.rs. You will need to:
    • Convert string operations like "+" and "/" to an Operation enum using a function parse_operation that returns an Option<Operation>.
    • Implement a function calculate that takes two numbers and an Operation, returning a Result<f64, CalcError> (which handles division by zero by returning an appropriate error).
    • Implement safe_calculator that both parses and operator and calculates a final value, returning Result<f64, CalcError>.
  3. Run the tests using cargo test to check your work
  4. Run the main function to see your code in action
  5. Make sure to commit and push your changes to your repository!

Tips

  • Use match statements for pattern matching on enums
  • Remember that match must be exhaustive (handle all cases)
  • For Option<T>, use Some(value) and None
  • For Result<T, E>, use Ok(value) and Err(error)
  • The #[derive(Debug, PartialEq)] attributes let you print and compare enum values

Starter code

#[derive(Debug, PartialEq)]
enum CalcError {
    DivisionByZero,
    InvalidOperation,
}

#[derive(Debug, PartialEq)]
enum Operation {
    Add,
    Subtract,
    Multiply,
    Divide,
}

// TODO: Implement these functions
fn parse_operation(op: &str) -> Option<Operation> {
    // Return Some(Operation) for "+", "-", "*", "/"
    // Return None for anything else
    todo!()
}

fn calculate(a: f64, b: f64, op: Operation) -> Result<f64, CalcError> {
    // Perform the calculation based on the operation
    // Return Err(CalcError::DivisionByZero) if dividing by zero
    // Hint: Check if b == 0.0 when op is Operation::Divide
    todo!()
}

fn safe_calculator(a: f64, op_str: &str, b: f64) -> Result<f64, CalcError> {
    // Combine parse_operation and calculate
    // Return Err(CalcError::InvalidOperation) if operation parsing fails
    // Hint: Use match on the output of parse_operation(op_str) - handle Some(operation) and None cases
    todo!()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_operation() {
        assert_eq!(parse_operation("+"), Some(Operation::Add));
        assert_eq!(parse_operation("-"), Some(Operation::Subtract));
        assert_eq!(parse_operation("*"), Some(Operation::Multiply));
        assert_eq!(parse_operation("/"), Some(Operation::Divide));
        assert_eq!(parse_operation("x"), None);
        assert_eq!(parse_operation(""), None);
    }

    #[test]
    fn test_calculate() {
        assert_eq!(calculate(10.0, 5.0, Operation::Add), Ok(15.0));
        assert_eq!(calculate(10.0, 3.0, Operation::Subtract), Ok(7.0));
        assert_eq!(calculate(4.0, 5.0, Operation::Multiply), Ok(20.0));
        assert_eq!(calculate(15.0, 3.0, Operation::Divide), Ok(5.0));
        assert_eq!(calculate(10.0, 0.0, Operation::Divide), Err(CalcError::DivisionByZero));
    }

    #[test]
    fn test_safe_calculator() {
        assert_eq!(safe_calculator(10.0, "+", 5.0), Ok(15.0));
        assert_eq!(safe_calculator(10.0, "/", 0.0), Err(CalcError::DivisionByZero));
        assert_eq!(safe_calculator(10.0, "x", 5.0), Err(CalcError::InvalidOperation));
    }
}

fn main() {
    // Test the calculator
    match safe_calculator(20.0, "/", 4.0) {
        Ok(result) => println!("Result: {}", result),
        Err(CalcError::DivisionByZero) => println!("Error: Cannot divide by zero!"),
        Err(CalcError::InvalidOperation) => println!("Error: Invalid operation!"),
    }

    // Run tests with: cargo test
}