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