Khair Alanam

Posted on

# Rust Tutorial 4: Let's build a Simple Calculator! (Part 1)

Welcome back to the Rust Tutorial Series!

In this tutorial, we will be building a simple calculator! On the way, we will learn some more concepts like functions, generics, tuples, arrays, and more!

This tutorial will be a 2-parter since we will be covering a heck load of concepts with just this simple project. The Rust Tutorial 5 will just be the second part of this tutorial.

So let's get started!

## Setup

• Run the `cargo new rust_calculator` command to create a new Rust project.

• Open the `rust_calculator` project in your code editor.

Let's start!

## Getting the user inputs

For our calculator, we want two numbers as the user input. So, let's set them up.

Go to the `main.rs` file and code this:

``````use std::io;

fn main() {
let mut x: String = String::new();
let mut y: String = String::new();

println!("Enter the first number: ");

println!("Enter the second number: ");

}
``````

Funnily enough, I had to refer to the previous tutorial to see how to get the user input because I forgot lol. So yeah ðŸ˜….

We also need the input to get the type of operation to be done between the given two numbers.

There are many ways to do this. Do remember that for all the cases, we will handle the errors in case of invalid input (Please refer to the previous tutorial for handling errors).

• We can ask the user for any of the four operations (`+`, `-`, `*`, `/`) directly.
• We can ask the user to enter the commands like `ADD`, `SUBTRACT`, `MULTIPLY`, and `DIVIDE`. These commands can be used to trigger the corresponding operation in our code.

Or here's my way:

• We show the set of operations the user can select in the terminal and then the user has to input the selection number. Then we can map that selection number to the operation we need.

So let's do that!

Here's the current code after asking for the operator input:

``````use std::io;

fn main() {
let mut x: String = String::new();
let mut y: String = String::new();
let mut op: String = String::new();

println!("Enter the first number: ");

println!("Enter the second number: ");

println!("List of operators:");
println!("(2) Subtract");
println!("(3) Multiply");
println!("(4) Divide");
println!("Select the number associated with the desired operation: ");

}
``````

Let's not forget to parse the number inputs for `x`, `y`, and `op`. Along with parsing the input, I will also handle the errors here using the `match` statement (Please refer to the previous tutorial to learn more about the `match` statement and handling errors)

``````use std::io;

fn main() {
let mut x: String = String::new();
let mut y: String = String::new();
let mut op: String = String::new();

println!("Enter the first number: ");
let x: i32 = match x.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return;
}
};

println!("Enter the second number: ");
let y: i32 = match y.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return;
}
};

println!("List of operators:");
println!("(2) Subtract");
println!("(3) Multiply");
println!("(4) Divide");
println!("Select the number associated with the desired operation: ");

}
``````

Notice that you will get warnings about unused variables. We'll ignore that for now.

Now that we have the operator input, let's use a `match` statement to see which number the operator corresponds to, do the operation, and store the result in some variable `result` (which we have to declare). If the number is not valid, we return some default case.

``````use std::io;

fn main() {
let mut x: String = String::new();
let mut y: String = String::new();
let result: i32;
let mut op: String = String::new();

println!("Enter the first number: ");
let x: i32 = match x.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return;
}
};

println!("Enter the second number: ");
let y: i32 = match y.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return;
}
};

println!("List of operators:");
println!("(2) Subtract");
println!("(3) Multiply");
println!("(4) Divide");
println!("Select the number associated with the desired operation: ");

let op: i32 = match op.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return;
}
};

match op {
1 => result = x + y,
2 => result = x - y,
3 => result = x * y,
4 => result = x / y,
_ => {
println!("Invalid selection");
return;
}
}

println!("The result is: {}", result);
}
``````

Now, let's run the code using the `cargo run` command.

Our calculator is working!

## Making a better calculator

Just like our previous project, we can make this calculator even better with much better readability and other options.

Firstly, if you notice, we are only doing calculations on integers and not on decimal numbers. We can change this by changing the parsing type of our inputs to `f64`.

``````use std::io;

fn main() {
// existing code

let x: f64 = match x.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return;
}
};

// existing code

let y: f64 = match y.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return;
}
};

// existing code
}
``````

All we did was change the parsing type to `f64` instead of `i32` for the variables `x` and `y`.

Now you can run the program and input decimals like 23.4 and 12.3.

Next, notice that the code for asking `x` and `y` inputs from the user is repetitive. Wouldn't it be nice to keep this code as some sort of a function so that we can reuse it?

## Functions

Functions are basically reusable blocks of code. They help in maintaining readability.

In Rust, since we have the `main` function, we will have to write our functions outside the `main` function.

A Rust function looks like this:

``````fn add_two(x: i32, y: i32) -> i32 {
return x + y;
}
``````

Some points to note:

• Functions are declared using the `fn` keyword.
• When including parameters, each parameter has to be declared with its corresponding type like `x` and `y` here in the example function.
• Notice that arrow `->` after the parameters? That basically means the return type of the function. Here, the result is the addition of two `i32` numbers `x` and `y` and we are returning an `i32` result. Hence, we define the return type as `i32` after the arrow `->`.

Let's get back to the project!

Let's make a function `input_parser` that takes `x` of type `String` as argument, assigns the user input, and parses the input to `f64`.

Here's the code for our function:

``````fn input_parser() -> f64 {
let mut x: String = String::new();
let x: f64 = match x.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return f64::NAN;
}
};

return x;
}
``````

What I basically did was copy the entire code for parsing the variable `x` and then return that `x`.

Also notice that for errors in parsing the input, we return something called a `NAN` (Not A Number). If you know JavaScript, then this should be familiar. We are using `f64::NAN` to return `NAN` as f64.

Now let's change the main code by using the `input_parser` function:

``````use std::io;

fn main() {
let result: f64;
let mut op: String = String::new();

println!("Enter the first number: ");
let x: f64 = input_parser();

println!("Enter the second number: ");
let y: f64 = input_parser();

println!("List of operators:");
println!("(2) Subtract");
println!("(3) Multiply");
println!("(4) Divide");
println!("Select the number associated with the desired operation: ");

let op: i32 = match op.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return;
}
};

match op {
1 => result = x + y,
2 => result = x - y,
3 => result = x * y,
4 => result = x / y,
_ => {
println!("Invalid selection");
return;
}
}

println!("The result is: {}", result);
}

fn input_parser() -> f64 {
let mut x: String = String::new();
let x: f64 = match x.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Invalid input!");
return f64::NAN;
}
};

return x;
}
``````

Notice how simple our main code is after we refactored the input and the parsing code into a function. Also, notice that we removed the declarations for `x` and `y` in the main code since `input_parser()` does that for us.

We forgot to handle the error in case we get one of our numbers as `f64::NAN`. So let's handle that in our main code:

``````use std::io;

fn main() {
// existing code

if f64::is_nan(x) {
println!("Invalid input!");
return;
}

// existing code

if f64::is_nan(y) {
println!("Invalid input!");
return;
}

// existing code
}
``````

We are using a built-in function called `f64::is_nan()` which accepts an `f64` parameter to check whether the given parameter is `NAN` or not.

Also, notice that `op` is just accepting any number in the given selection (1 to 4) and can be considered as `f64`. So let's change the types from `i32` to `f64`. The reason we are doing this is just so that we can use our `input_parser()` function for the `op` variable to not repeat ourselves.

``````    println!("List of operators:");
println!("(2) Subtract");
println!("(3) Multiply");
println!("(4) Divide");
println!("Select the number associated with the desired operation: ");

let op: f64 = input_parser();

if f64::is_nan(op) {
println!("Invalid input!");
return;
}
``````

Just like the `x` and `y` variables, we use `input_parser()` for the `op` variable too. Also, you can remove the `op` variable declaration in the main code since `input_parser()` takes care of that.

After doing this, you will notice that there are errors related to mismatching types for `f64` and integer in the `match` statement for `op`. We can fix this by converting the `op` variable's type from `f64` to `i32` like this:

``````    let op: f64 = input_parser();

if f64::is_nan(op) {
println!("Invalid input!");
return;
}

let op: i32 = op as i32;
``````

Here, by the concept of shadowing, we re-declare `op` as an `i32` variable. Then we assign the value of the previous `op` variable. But since the new `op` and old `op` have different types, we explicitly convert the old `op` variable type to `i32`, which is what `as i32` basically means. This is called type casting and is pretty essential when it comes to explicit conversions of data types.

Now that everything's done, let's see the final code:

``````use std::io;

fn main() {
let result: f64;

println!("Enter the first number: ");
let x: f64 = input_parser();

if f64::is_nan(x) {
println!("Invalid input!");
return;
}

println!("Enter the second number: ");
let y: f64 = input_parser();

if f64::is_nan(y) {
println!("Invalid input!");
return;
}

println!("List of operators:");
println!("(2) Subtract");
println!("(3) Multiply");
println!("(4) Divide");
println!("Select the number associated with the desired operation: ");

let op: f64 = input_parser();

if f64::is_nan(op) {
println!("Invalid input!");
return;
}

let op: i32 = op as i32;

match op {
1 => result = x + y,
2 => result = x - y,
3 => result = x * y,
4 => result = x / y,
_ => {
println!("Invalid selection");
return;
}
}

println!("The result is: {}", result);
}

fn input_parser() -> f64 {
let mut x: String = String::new();
let x: f64 = match x.trim().parse() {
Ok(num) => num,
Err(_) => {
return f64::NAN;
}
};

return x;
}
``````

Now you can run the code using the `cargo run` command and test for some inputs.

Great job! you have made a simple calculator that can do basic math operations!

In the next tutorial which is just the second part of this tutorial, we will be making a better calculator and on the way, learn some more new concepts!

Until then, have a great day!

GitHub Repo: https://github.com/khairalanam/rust-calculator

If you like whatever I write here, follow me on Devto and check out my socials:

I also have a new portfolio!
Check it out: https://khairalanam.carrd.co/