🎓 A1Learner
🔍

Rust Calculator Project

The calculator project is a small command line tool that does one sum. The tool runs like calculator 138 / 4.

Step 1: take the arguments one at a time

env::args() hands the words out in order. nth(1) steps past the program name to the first real argument. After that, nth(0) takes the next one each time:

let mut args = std::env::args();
let first = args.nth(1).unwrap();
let operator = args.nth(0).unwrap();
let second = args.nth(0).unwrap();
println!("{} {} {}", first, operator, second);

Step 2: parse the text into numbers

parse turns text into a number, and the turbofish ::<f32> says which kind of number. It hands back a Result, so a bad number is a case you can handle instead of a crash:

let first_number = first.parse::<f32>().unwrap();
let second_number = second.parse::<f32>().unwrap();

Step 3: one match does the maths

The operator arrives as text. Take its first char and match on it. There is one arm per operation. x is accepted next to *:

fn operate(operator: char, a: f32, b: f32) -> f32 {
    match operator {
        '+' => a + b,
        '-' => a - b,
        '/' => a / b,
        '*' | 'x' | 'X' => a * b,
        _ => panic!("Invalid operator used."),
    }
}

Step 4: build the answer as text

format! assembles the final sentence as a String first, so the printing line itself stays trivial:

fn output(a: f32, operator: char, b: f32, result: f32) -> String {
    format!("{} {} {} = {}", a, operator, b, result)
}

Example

use std::env;
use std::process;

fn main() {
    let mut args = env::args();
    let first = args.nth(1).unwrap_or_else(|| {
        eprintln!("usage: calculator <number> <operator> <number>");
        process::exit(2);
    });
    let operator_text = args.nth(0).unwrap_or_else(|| {
        eprintln!("usage: calculator <number> <operator> <number>");
        process::exit(2);
    });
    let second = args.nth(0).unwrap_or_else(|| {
        eprintln!("usage: calculator <number> <operator> <number>");
        process::exit(2);
    });

    let a = parse_number(&first);
    let b = parse_number(&second);
    let operator = operator_text.chars().next().unwrap();
    let result = operate(operator, a, b);
    println!("{}", output(a, operator, b, result));
}

fn parse_number(text: &str) -> f32 {
    text.parse::<f32>().unwrap_or_else(|_| {
        eprintln!("error: '{}' is not a number", text);
        process::exit(2);
    })
}

fn operate(operator: char, a: f32, b: f32) -> f32 {
    match operator {
        '+' => a + b,
        '-' => a - b,
        '/' => a / b,
        '*' | 'x' | 'X' => a * b,
        _ => {
            eprintln!("error: unknown operator '{}'", operator);
            process::exit(2);
        }
    }
}

fn output(a: f32, operator: char, b: f32, result: f32) -> String {
    format!("{} {} {} = {}", a, operator, b, result)
}

Key points

  • Plan the shape first.
  • Read three arguments: number, operator, number.
  • Turn the text into f32 values.
  • Match on the operator.
  • Print one finished sentence.
  • Stepping through args has a trap: nth() consumes.
  • args.nth(1) skips the program name.
  • It lands on the first real argument.
  • After that, nth(0) steps just one place each time.
  • A failed parse is ordinary user error, not a bug in your program: print a friendly message with eprintln! and leave with std::process::exit(2) instead of panicking
  • A terminal treats * as a pattern that matches file names.
  • So a shell may swallow it before your program ever sees it.
  • Accepting x as a second spelling of multiply keeps the tool usable: calculator 6 x 7
  • The calculator project builds a small command line tool that works out one sum from three arguments.

In this example, the program is run as calculator 138 / 4. nth(1) skips the program name and lands on 138. Two nth(0) calls step to / and then 4. parse_number turns the two texts into f32 values. Had either failed, the program would print a friendly error. It would exit with code 2. operate matches on the char / and divides, giving 34.5. output builds the sentence 138 / 4 = 34.5 with format!, and main prints it. Try calculator 6 x 7 as well: the x arm gives 6 x 7 = 42.

Note

This is the hand-rolled style on purpose.

The Clap lesson earlier in this course shows the crate real tools reach for.

They reach for it once flags, subcommands and help text appear.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why does the program read args.nth(1) first, and nth(0) after that?