Originally published on php2rust.dev — part of the PHP to Rust course. View this lesson on the original site.

The calculator will:

  • Prompt for two numbers
  • Ask for an operation (+, -, *, or /)
  • Perform the calculation and display the result

Replace the contents of src/main.rs with this code:

use std::io;

fn main() {
    println!("Welcome to the Rust CLI Calculator!");

    let num1 = get_number("Enter the first number: ");
    let num2 = get_number("Enter the second number: ");

    println!("Enter an operation (+, -, *, /):");
    let mut operation = String::new();
    io::stdin().read_line(&mut operation).expect("Failed to read input");
    let operation = operation.trim();

    let result = match operation {
        "+" => Some(num1 + num2),
        "-" => Some(num1 - num2),
        "*" => Some(num1 * num2),
        "/" => {
            if num2 != 0.0 {
                Some(num1 / num2)
            } else {
                println!("Error: Division by zero is not allowed.");
                None
            }
        }
        _ => {
            println!("Invalid operation. Please enter +, -, *, or /.");
            None
        }
    };

    if let Some(res) = result {
        println!("Result: {}", res);
    }
}

fn get_number(prompt: &str) -> f64 {
    loop {
        println!("{}", prompt);
        let mut input = String::new();
        io::stdin().read_line(&mut input).expect("Failed to read input");

        match input.trim().parse::<f64>() {
            Ok(num) => return num,
            Err(_) => println!("Invalid number. Please enter a valid numeric value."),
        }
    }
}

PHP equivalent

If you're coming from PHP, here's the same logic in PHP. The flow is identical; the syntax differs:

<?php
function get_number(string $prompt): float {
    while (true) {
        echo $prompt;
        fflush(STDOUT);
        $input = fgets(STDIN);
        if ($input === false) {
            echo "Failed to read input.\n";
            continue;
        }
        $trimmed = trim($input);
        if (is_numeric($trimmed)) {
            return (float) $trimmed;
        }
        echo "Invalid number. Please enter a valid numeric value.\n";
    }
}

echo "Welcome to the PHP CLI Calculator!\n";
$num1 = get_number("Enter the first number: ");
$num2 = get_number("Enter the second number: ");
echo "Enter an operation (+, -, *, /): ";
fflush(STDOUT);
$operation = trim(fgets(STDIN) ?: '');

$result = null;
switch ($operation) {
    case '+': $result = $num1 + $num2; break;
    case '-': $result = $num1 - $num2; break;
    case '*': $result = $num1 * $num2; break;
    case '/':
        if ($num2 != 0) {
            $result = $num1 / $num2;
        } else {
            echo "Error: Division by zero is not allowed.\n";
        }
        break;
    default:
        echo "Invalid operation. Please enter +, -, *, or /.\n";
}

if ($result !== null) {
    echo "Result: $result\n";
}

Comparing the two languages

Same story in both files: prompt → read → trim → validate → compute → print. Each topic below has a short heading and a Rust | PHP row.

Read a line

Rust PHP
io::stdin().read_line(&mut s) appends into an existing String (mut / &mut). Returns a Result, here .expect(...) stops on failure. Input includes the newline → use .trim(). fgets(STDIN) returns a string or false. Check $input === false before trim() so you never treat a failed read as text. Same idea as handling I/O errors; Rust encodes it in Result.

Prompts and fflush

Rust PHP
Number prompts use println!(...), which adds a newline; you usually see output before the read blocks. The operation line uses println! for the label, then read_line on the next line, layout differs slightly from PHP’s single-line prompt. After echo "...: " with no trailing newline, fflush(STDOUT) forces the prompt onto the terminal before fgets blocks (CLI output buffering).

Parse to number

Rust PHP
input.trim().parse::<f64>() → Result<f64, _>. match: Ok(num) → return num; Err(_) → print error, loop again. trim, then is_numeric. If true, (float) $trimmed or floatval(). Otherwise print error; while (true) retries.

The get_number loop

Rust PHP
loop { ... } until return. New String::new() each try, match on parse: success exits the function; failure continues the loop. while (true): continue on bad read or bad number; return only when input is valid. Same “retry until good” shape.

Choosing +, -, *, or /

Rust PHP
match operation { ... } on string literals. Arms yield Some(f64) or None (bad op / divide by zero). _ = catch-all, like default. match is exhaustive (compiler checks you covered cases). switch ($operation) with case / break. default for unknown ops. Division by zero: echo error, $result stays null.

Optional result

Rust PHP
Option<f64>: Some(value) or None. if let Some(res) = result { ... } prints only when there is a number, types stay explicit. $result = null, assign on success. if ($result !== null) before printing, same idea as if let Some.

Run

Rust PHP
From the crate root: cargo run (build + run). php cli_calculator.php (or your script name) from the project directory.

Big picture: same pipeline, input → parse/validate → branch → maybe print. Rust leans on static types, Result, and Option so more bugs show up at compile time; PHP stays looser at runtime. This lesson is still a preview; later material goes deeper on std::io, match, Result, and .parse().

What's next

Next: Step 3: Running the program.

From PHP to Rust — Getting Started with Rust