Rust Tutorial 3: Handling Errors and Other Concepts
Tags: rust, programming, tutorial, beginners
Published: 2023-09-29 17:01:57

_Reading time: 11 minutes_

Welcome to the Rust Tutorial 3!

This will be a short tutorial and is a continuation of the previous tutorial.

In this tutorial, we will handle errors in our guessing game and also learn some more concepts on the way.

So let's get started!

---

### `const` keyword

Just like the `let` keyword, `const` is used to declare variables in Rust that will never be changed through out the program.

One key thing you have to know about `const` variables is that, we cannot mutate these variables, even if you use the `mut` keyword. Using the `mut` keyword in a `const` variable will throw an error.

Also, it's a convention in Rust to use upper snake case names for `const` variables.

Let's just use an example:

``` rust
fn main() {
    const SOME_NUMBER: i32 = 100_000_000;
}
```

*Fun fact: you can use underscores in an integer if the given number is huge for better readability.*

99% of the time, you will be using the `let` keyword. So just use that. If you are really sure that your variable doesn't change at all in the main program execution, then you may use `const`.

---

### `match` case

The `match` case statement in Rust is very similar to the `switch` case statements you see in other programming languages. A `match` case is a clean way of writing multiple `if` statement that use the same variable to check for some value. An example looks like this:

``` rust
fn main() {
    let age: i32 = 30;

    match age {
        1 => println!("One!"),
        2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
        13..=19 => println!("A teen"),
        _ => println!("Just some number"),
    }
}
```

In the `match` statement, each value in the LHS of the `=>` expression represents the cases of the `match` statement, separated by commas. For instance, If age is 1, then we print "One!".

We can chain multiple cases using the pipe `|` expression if many values give the same output. You can also use a range of numbers as a case.

In case none of the numbers match, we use underscore `_` to define a default case.

---

### `match` case with variable assignments

Let's take a look at this program:

``` rust
fn main() {
    let age: i32 = 30;
    let res: &str;

    match age {
        1..=10 => res = "Child",
        11..=18 => res = "Teen",
        20..=30 => res = "Young Adult",
        _ => res = "Adult",
    }

    println!("{}", res);
}
```

This program prints out the group type based on age using `match` case. But notice that in every case, I am assigning a string to the variable `res`. You can see that it seems repetitive.

Well there's a way to make it DRY. Checkout this one:

``` rust
fn main() {
    let age: i32 = 30;

    let res: &str = match age {
        1..=10 => "Child",
        11..=18 => "Teen",
        20..=30 => "Young Adult",
        _ => "Adult",
    };

    println!("{}", res);  // prints "Young Adult"
}
```

All I did was declare the `res` variable and assign it to the `match` case statement based on the `age` value.

Seems pretty intuitive. Right?

---

### Block statements and Block scope

There are lot of syntactic sugar in Rust that you wouldn't see in other programming languages. Though all the programming languages has some kind of syntactic sugar, Rust has a unique way of handling them. Block statements are one of them.

Block statements are pretty useful when you want to assign a value to some variable "once" (thus not needing a separate function) but requires some complex calculations to get that value.

A major advantage of block statements is that any variable declared inside the block statement are scoped to that block only. This helps in keeping the program consistent and prevent the program from any kind of scope errors.

Let's take an example:

``` rust
fn main() {
    let age: i32 = 30;

    {
        let age: i32 = 24;
        println!("Block Age: {}", age);
    }

    println!("Global Age: {}", age);
}
```

Running the code will give this output:

``` shell
Block Age: 24
Global Age: 30
```

You would expect that the `age` would change to 24 outside the block due to shadowing, but that's not what happened.

Due to block scope, the `age` variable declared inside the block will not affect anything outside the block.

Except, if you are mutating the `age` variable. Like this:

``` rust
fn main() {
    let mut age: i32 = 30;

    {
        age += 24;
        println!("Block Age: {}", age);
    }

    println!("Global Age: {}", age);
}
```

This will print:

``` shell
Block Age: 54
Global Age: 54
```

So, yea. If the variable is being declared as mutable, then block statements can change the variables outside the block scope.

Now let's get to an interesting part.

---

### Assigning block statements to the variables.

Let's write this code:

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

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

In this code:
* I declared a variable called `some_num`.
* Then I declared a block statement in which I declared two local variables `x` and `y` and assigned some values.
* Then I add them `x + y` and assign the sum to `some_num`.

This will print out "79" in the console.

Now, you might be confused with the `x + y` term that seems out of place compared to the rest of code. Basically what's happening is that after I add `x` and `y`, since there's no other code remaining, The block will return this value, i.e. `x + y`, and assign it to `some_num`.

Also, notice that I didn't put a semi-colon after `x + y`. This is because if you used a semi-colon after `x + y` then the value cannot get assigned to the variable and will return some empty block.

---

### Some more loops!

Let's get back to the loops! In the previous tutorial, I talked about the `loop` expression, the simplest of the loops. Now let's learn the `for` and `while` loop.

A `while` loop in Rust looks like this:

``` rust
fn main() {
    let mut i: i32 = 0;

    while i < 5 {
        println!("{}", i);
        i += 1;
    }
}
```

A `for` loop in Rust looks like this:

``` rust
fn main() {
    for i in 1..=5 {
        println!("{}", i);
    }
}
```

Notice we have used a range in a `for` loop.

---

## Making a Better Guessing Game

In the previous tutorial, we have made our guessing game. But you might have noticed that we can guess as many times as you want and the game wouldn't end.

So let's implement a guess limiter to our game.

To do that, we can simply replace the `loop` expression with a `while` loop expression. For our game, we can let the player have at maximum 5 guesses. 

Here's how the code will look like:

``` rust
use std::io;
use rand::Rng;

fn main() {
    let secret_number: i32 = rand::thread_rng().gen_range(1..101);
    let mut i: i32 = 0;

    while i < 5 {
        println!("Guess {}", i + 1);
        println!("Guess the number between 1 and 100: ");
        let mut user_input: String = String::new();

        io::stdin().read_line(&mut user_input).expect("Some Input Error.");

        let user_input: i32 = user_input.trim().parse().expect("Not an integer");

        if user_input < secret_number {
            println!("Too small!");
        } else if user_input > secret_number {
            println!("Too big!");
        } else {
            break;
        }

        i += 1;
    }

    if i == 5 {
        println!("You have lost! The number was {}!", secret_number);
    } else {
        println!("You guessed it! It's {}!", secret_number);
    }
}
```

The changes that I made here are:
* I declared a mutable variable `i` with initial value as 0.
* I replaced the `loop` with a `while` loop when i < 5 (5 guesses).
* I added a print statement to keep track of the guesses.
* Once the `while` loop is over, whether normally or using `break` statement, it will go through an `if` block (outside the `while` loop) to see if `i` is equal to 5 or not and then set the print statements for each condition.

You can now play the game and you will notice you will only have five guesses to guess the number.

---

Now let's play the game, but this time let's input some string instead of an integer as our guess.

Let's run the `cargo run` command and see the output:


![Rust error output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kj221kwavpqn6zmjzl0v.png)


We get an error since we input a string and not an integer. We have to handle these errors in our code.

Let's take a look at our code:

![Rust code analysis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/conyy6r7b13xpdktf5yy.png)

If you observe, the line of code pointed by the arrow is where we are getting the error. We need to modify this line to handle our errors.

What we can do is, if the user inputs a valid input, then it's all okay. But if the user inputs an invalid input, we can notify the user that it's an invalid input and then restart the loop.

We can do this using match case statement for the `user_input` variable.

But what will be the variable which we will match the values to?

That will be the `user_input.trim().parse()`.

We know that this function will either return a valid result or an error.

We can verify the return value of the function by using these two enums:
* `Ok()` to see if it's a valid result, and
* `Err()` to see if it's an error.

These two enums will be our cases for matching `user_input.trim().parse()` and then assign that to the `user_input`. 

Let's modify that one line of code to this:

``` rust
let user_input: i32 = match user_input.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("Invalid Input! Try again!");
                continue;
            },
        };
```

In this code, The first case is when the result is valid. We first check whether the `user_input.trim().parse()` returns an integer, say `num`. The `Ok(num)` will verify if it's a valid result.

The second case is when `user_input.trim().parse()` returns an Error object. Since, we will check for any kind of errors, we use an underscore `_` inside `Err()` to verify for any errors.

Also, notice that I removed `expect()` function since we are already handling the errors in a better way.

---

Now let's run the code and see the output:


![Final Rust output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ynktqmnq710m8vk81t41.png)

As you can notice, I have input some invalid inputs to test the game and it works!

Great job! You have made a better guessing game!

---

I hope you have learnt a lot in this short tutorial. As I have said, I would have included this in the previous tutorial but that one was getting too long and hence, I decided to write a new one just for this.

In the next tutorial, we will make a simple calculator using Rust.

Until next time, have an awesome day ahead!

GitHub Repo: https://github.com/khairalanam/better-rust-guessing-game

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