RsRust · Lesson 7 of 8

Mini Project: CLI Calculator

Let's build a command-line calculator using everything we've learned: structs, enums, error handling, and pattern matching.

Rust
use std::env;
use std::fmt;

#[derive(Debug)]
enum CalcError {
    InvalidNumber(String),
    InvalidOperator(String),
    DivisionByZero,
    MissingArguments,
}

impl fmt::Display for CalcError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            CalcError::InvalidNumber(s) => write!(f, "Invalid number: '{}'", s),
            CalcError::InvalidOperator(s) => write!(f, "Unknown operator: '{}'", s),
            CalcError::DivisionByZero => write!(f, "Cannot divide by zero"),
            CalcError::MissingArguments => write!(f, "Usage: calc <num> <op> <num>"),
        }
    }
}

fn calculate(a: f64, op: &str, b: f64) -> Result<f64, CalcError> {
    match op {
        "+" => Ok(a + b),
        "-" => Ok(a - b),
        "*" | "x" => Ok(a * b),
        "/" => {
            if b == 0.0 {
                Err(CalcError::DivisionByZero)
            } else {
                Ok(a / b)
            }
        }
        "%" => Ok(a % b),
        "^" => Ok(a.powf(b)),
        _ => Err(CalcError::InvalidOperator(op.to_string())),
    }
}

fn run() -> Result<(), CalcError> {
    let args: Vec<String> = env::args().collect();

    if args.len() != 4 {
        return Err(CalcError::MissingArguments);
    }

    let a: f64 = args[1].parse().map_err(|_| CalcError::InvalidNumber(args[1].clone()))?;
    let op = &args[2];
    let b: f64 = args[3].parse().map_err(|_| CalcError::InvalidNumber(args[3].clone()))?;

    let result = calculate(a, op, b)?;
    println!("{} {} {} = {}", a, op, b, result);
    Ok(())
}

fn main() {
    if let Err(e) = run() {
        eprintln!("Error: {}", e);
        std::process::exit(1);
    }
}
Bash
cargo run -- 10 + 5
cargo run -- 10 / 0
cargo run -- 2 ^ 10
cargo run -- 15 % 7
◆ Note
Notice how every error path is explicit and handled. The ? operator makes propagation clean without hiding anything. This is the Rust way: errors are values, not surprises.