Calculator

Build a small calculator using enums, match, and Result based error handling.

1

Core concepts

enum, struct, match, Result, error handling

2

How the learner should approach this project

Do not paste the complete program immediately. Create the file, type the first step, run vpp check, and then run the program when a complete entry point exists. Read the explanation before looking at the snippet. After each step, predict what the new code should do. This turns the page into a lesson rather than a code dump.

3

Step 1: Define operations

The Op enum limits the calculator to four legal operations. This makes the operation selection explicit and lets match be checked for completeness.

After typing the snippet, identify the new names introduced by the step. Ask what each name represents, what type the compiler should infer or check, and what value should exist after the code runs.

main.vpp
enum Op {
    Add
    Sub
    Mul
    Div
}
4

Step 2: Store an operation

CalcOp wraps the selected operation in a named struct. This creates a natural value to pass into the calculation function.

Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.

main.vpp
struct CalcOp {
    op: Op
}
5

Step 3: Apply the operation safely

The apply function matches every operation. Division receives an additional runtime condition because division by zero is an error. Result lets the caller handle that error explicitly.

Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.

main.vpp
fn apply(calc: CalcOp, a: int, b: int) -> Result<int, string> {
    match calc.op {
        Add => {
            return Ok(a + b)
        }
        Sub => {
            return Ok(a - b)
        }
        Mul => {
            return Ok(a * b)
        }
        Div => {
            if b == 0 {
                return Err("divide by zero")
            }
            return Ok(a / b)
        }
    }
}
6

Step 4: Run the calculator

The caller receives a Result and matches it. Ok contains the calculated integer. Err contains the explanation when an operation cannot be completed.

Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.

main.vpp
fn main() -> int {
    let calc = CalcOp { op: Mul }
    let r = apply(calc, 6, 7)
    match r {
        Ok(n) => {
            print(n)
        }
        Err(e) => {
            print(e)
        }
    }
    return 0
}
7

Complete source

main.vpp
enum Op {
    Add
    Sub
    Mul
    Div
}
struct CalcOp {
    op: Op
}
fn apply(calc: CalcOp, a: int, b: int) -> Result<int, string> {
    match calc.op {
        Add => {
            return Ok(a + b)
        }
        Sub => {
            return Ok(a - b)
        }
        Mul => {
            return Ok(a * b)
        }
        Div => {
            if b == 0 {
                return Err("divide by zero")
            }
            return Ok(a / b)
        }
    }
}
fn main() -> int {
    let calc = CalcOp { op: Mul }
    let r = apply(calc, 6, 7)
    match r {
        Ok(n) => {
            print(n)
        }
        Err(e) => {
            print(e)
        }
    }
    return 0
}
8

Expected behavior

The complete program should produce the following output when run with the command shown below. Exact formatting should follow the current V++ runtime.

42
9

Run the project

terminal
vpp run projects/15-calculator/main.vpp
10

What the learner should understand after this project

A calculator is a useful example of combining enums, match, structs, and Result into a small domain model.

11

Common mistakes to teach

  1. Leaving an enum operation unhandled
  2. Dividing by zero
  3. Printing a Result without matching its alternatives
12

Practice extension

Run the calculator with Div and b equal to zero. Confirm that Err is handled without crashing.

A strong learner should be able to explain the program without looking at the code, rebuild the core idea from memory, and make the practice change without copying a solution.

13

Full program

main.vpp
enum Op {
    Add
    Sub
    Mul
    Div
}
struct CalcOp {
    op: Op
}
fn apply(calc: CalcOp, a: int, b: int) -> Result<int, string> {
    match calc.op {
        Add => {
            return Ok(a + b)
        }
        Sub => {
            return Ok(a - b)
        }
        Mul => {
            return Ok(a * b)
        }
        Div => {
            if b == 0 {
                return Err("divide by zero")
            }
            return Ok(a / b)
        }
    }
}
fn main() -> int {
    let calc = CalcOp { op: Mul }
    let r = apply(calc, 6, 7)
    match r {
        Ok(n) => {
            print(n)
        }
        Err(e) => {
            print(e)
        }
    }
    return 0
}
vpp
$ ready. Click Test program.