Rust Tutorial 5: Let's Build a Simple Calculator! (Part 2)
Tags: rust, tutorial, programming, beginners
Published: 2023-12-28 10:15:56

_Reading Time: 15 minutes_

Welcome back to the Rust Tutorial Series!

We will be continuing the simple calculator project, and on the way learn new concepts like tuples, arrays, etc.

So, let's get started!

![Semicolon and Code meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uknxbp1g16w6prb4gw6o.png)

---

## More functions!

Going back to the code, notice that for the operations we do, we are doing them in our main code and not refactored into separate functions.

Now for this project, our functions are all one-liners. But it's a good practice to keep operations as separate functions so that we can maintain the readability of our main program code.

So, let's write functions for each of the operations:

``` rust
fn add(x: f64, y: f64) -> f64 {
    return x + y;
}

fn subtract(x: f64, y: f64) -> f64 {
    return x - y;
}

fn multiply(x: f64, y: f64) -> f64 {
    return x * y;
}

fn divide(x: f64, y: f64) -> f64 {
    return x / y;
}
```

Now, let's replace each of the operations in the `match` statement `op` with these functions:

```
use std::io;

fn main() {

    // rest of the code

    match op {
        1 => result = add(x, y),
        2 => result = subtract(x, y),
        3 => result = multiply(x, y),
        4 => result = divide(x, y),
        _ => {
            println!("Invalid selection");
            return;
        }
    }

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

Now that looks good. Let's get back to our four functions again.

Remember this block of code from a previous tutorial?

``` rust
fn main() {
    let some_num: i32 = {
        let x: i32 = 45;
        let y: i32 = 34;
        x + y
    };

    println!("{}", some_num);
}

```

And remember that odd-looking `x + y` line of code that returns itself to the `some_num` variable? Well, we can do that in functions too! 

In our four functions, we can remove the `return` keyword and the semi-colon to just do the same thing:

``` rust
fn add(x: f64, y: f64) -> f64 {
    x + y
}

fn subtract(x: f64, y: f64) -> f64 {
    x - y
}

fn multiply(x: f64, y: f64) -> f64 {
    x * y
}

fn divide(x: f64, y: f64) -> f64 {
    x / y
}
```

These are still valid functions! These "naked returns" can be tricky to someone who isn't used to Rust. But just know that Rust can do this :D

---

Let's take a look at this part of the code we wrote:

``` rust
match op {
        1 => result = add(x, y),
        2 => result = subtract(x, y),
        3 => result = multiply(x, y),
        4 => result = divide(x, y),
        _ => {
            println!("Invalid selection");
            return;
        }
    }
```

Notice that the numbers are not random and they are ordered?

Wouldn't it be nice to keep them in an ordered sequence in such a way that I can just access them via "indices", and get rid of the entire `match` statement completely?

That's where tuples and arrays come in!

---

## Tuples

Tuples are compound data structures that can hold different types of data. These data are enclosed in parentheses `()`. They are fixed in length.

``` rust
    let my_tup = (1, true, "Hello");
```

To add types for a tuple, you have to specify the type for each data inside the tuple in the exact order, enclosed in parentheses like this:

``` rust
    let my_tup: (i32, bool, &str) = (1, true, "Hello");
```

We will get to know more about tuples in the later sections.

---

## Arrays

Arrays are just like tuples in many ways, except unlike tuples, they can only hold values of the same data type. These data are enclosed in square brackets `[]`. They are also fixed in length.

``` rust
    let my_arr = [1, 2, 3, 4, 5];
```

To add types for an array, you have to specify two parameters enclosed in square brackets; the first is the data type for each value in the array, and the second is the length of the array. These parameters are separated by a semi-colon. Here's what it looks like:

``` rust
    let my_arr: [i32; 5] = [1, 2, 3, 4, 5];
```

We will get to know more about arrays in the later sections.

---

## Accessing values in tuples and arrays

If you ever come across accessing values in arrays or lists in other programming languages, then it is almost identical here in Rust. Rust follows zero-indexing, that is the first element is in the 0th index, the second element is in the 1st index, and so on.

However, The syntax to access values in arrays is very different from that of tuples. Let me show you:

``` rust
fn main() {
    let my_arr: [i32; 5] = [1, 2, 3, 4, 5];
    let my_tup: (i32, f64, bool) = (23, 16.4, false);

    println!("{}", my_arr[0]);  // prints 1
    println!("{}", my_tup.1);   // prints 16.4
}
```

If you notice, we use square brackets to access any element in an array based on the given index. However, in tuples, we use dot notation.

Also, just a quick note, if you want to print the entire tuple or array in one print statement. We have to use the `{:?}` formatting instead of `{}` to do the same:

``` rust
    let my_arr: [i32; 5] = [1, 2, 3, 4, 5];
    let my_tup: (i32, f64, bool) = (23, 16.4, false);

    println!("{:?}", my_arr);
    println!("{:?}", my_tup);
```

Now let's get back to our project.

---

Let's create an array of length four to enclose all the four operations:

``` rust
let ops = [add, subtract, multiply, divide];
```

Remember when adding the function names in the array or tuple, you should not use parentheses along with the function name.

Now you might be thinking, what will be the data type of a function?

It's not necessary to mention types during declaration as Rust infers the type from the given value. However, here's what it looks like:


``` rust
let ops: [fn(f64, f64) -> f64; 4] = [add, subtract, multiply, divide];
```

Let's break down the function type:
* Firstly, we have the `fn` keyword to signify that the values given inside the `ops` array are functions.
* Then we have two `f64`s enclosed inside the `fn` keyword. This is to signify the data types of parameters of these four functions. If you remember, all these four functions take two decimals as arguments which are of `f64` type.
* Lastly, we have the `f64` after an arrow `->`. This is the return type of the function. As you can notice, every function in the given array returns a decimal of type `f64`.

That's pretty much the breakdown. To summarise this bit, if you are including the functions in your array, make sure they have these checked:
* All the values are of the `fn` type
* The number of parameters AND the datatypes of each of the parameters IN the exact order are the same for every function.
* Finally, the return type for each function has to be the same.

---

Now that we have our operations array, we can completely remove the `match` case statement and just use the desired operation based on the selection given:

So let's write this one line of code by replacing the `match` case statement:

``` rust
result = ops[op - 1];
```

You will get an error saying that you cannot use an `i32` as an index. So for indexing, you have to use the type casting to convert `op - 1`'s type of `i32` to something called `usize` which is the type defined for those variables that act as pointers to the array elements.

It will look like this:

``` rust
result = ops[(op - 1) as usize];
```

Later, you will get a mismatched type error. This is because the `result` variable is of type `f64` but you are assigning an array element of type `fn`, hence the mismatched type.

So to fix this, you have to "call" the operation along with arguments `x` and `y` using parentheses.

If the above sounds confusing, here's what it looks like:

``` rust
result = ops[(op - 1) as usize](x, y);
```

Finally, we have to put an `if` check to see if the `op` lies in the range of 1 to 4 before assigning the result to the `result` variable, or else we will get the "out of range" error.

``` rust
if op > 4 || op < 1 {
    println!("Invalid Selection!");
    return;
}

result = ops[(op - 1) as usize](x, y);
```

Now, the final code will look like this:

``` rust
use std::io;

fn main() {
    let result: f64;
    let ops: [fn(f64, f64) -> f64; 4] = [add, subtract, multiply, divide];

    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!("(1) Add");
    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;

    if op > 4 || op < 1 {
        println!("Invalid Selection!");
        return;
    }

    result = ops[(op - 1) as usize](x, y);

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

fn input_parser() -> f64 {
    let mut x: String = String::new();
    io::stdin().read_line(&mut x).expect("Invalid Input");
    let x: f64 = match x.trim().parse() {
        Ok(num) => num,
        Err(_) => {
            return f64::NAN;
        }
    };

    return x;
}

fn add(x: f64, y: f64) -> f64 {
    x + y
}

fn subtract(x: f64, y: f64) -> f64 {
    x - y
}

fn multiply(x: f64, y: f64) -> f64 {
    x * y
}

fn divide(x: f64, y: f64) -> f64 {
    x / y
}
```

---

Now let's make this calculator work as much as we want to by wrapping the logic inside a `loop` statement.

Here's the final code:

``` rust
use std::io;

fn main() {
    let mut result: f64;
    let mut y_or_n: String = String::new();
    let ops: [fn(f64, f64) -> f64; 4] = [add, subtract, multiply, divide];

    loop {
        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!("(1) Add");
        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;

        if op > 4 || op < 1 {
            println!("Invalid Selection!");
            return;
        }

        result = ops[(op - 1) as usize](x, y);

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

        println!("Continue? (y/n)");
        io::stdin().read_line(&mut y_or_n).expect("Invalid Input");

        if y_or_n.trim() == "n" {
            break;
        }
    }
}
```

Here's what I did:
* I declared `result` as mutable using `mut`.
* I declared a new variable `y_or_n` to check for loop condition whether to continue or not.
* Then I wrapped the entire calculation logic inside a `loop` statement, except the first three declarations.
* After the `result` printing, I take the user input for `y_or_n`. Since we want a string, we don't have to parse it for integer input.
* Finally I check for the condition comparing the input with `"n"`. I used the `trim()` function because when you press <kbd>Enter</kbd> key to finish the input, that key press is also recorded in the input (as `\n`). So to remove that `\n`, we use the `trim()` function.

---

That's pretty much it! Now you should probably be good with the Rust basics with just this simple calculator project!

In the next tutorial, we will explore one of the most important concepts of Rust and that is the concept of ownership and how memory management works in Rust.

Until then, have a great day ahead!

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

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

LinkedIn: https://www.linkedin.com/in/khair-alanam-b27b69221/
Twitter: https://www.twitter.com/khair_alanam
GitHub: https://github.com/khairalanam

I also have a new portfolio! I am a web developer as well as a web designer with 3+ years of experience designing creative web experiences for everyone.

Check it out: https://khairalanam.carrd.co/