Learnings from Advent Of Code Day 1 as a Rust Newbie
Tags: rust, adventofcode, programming
Published: 2022-12-10 22:44:20

I've always been curious to learn a little bit more about Rust and its properties, so I decided to take part in the Advent Of Code challenge and do it in Rust! In this post, I'm going to share my learnings about Rust as a newbie going through the first challenge in Advent Of Code. Here is the day1 challenge: https://adventofcode.com/2022/day/1. I'd encourage looking through it so that you can understand what the code below is intended to do.

First, here is the code that you can look over:

```rust
use std::collections::BinaryHeap;
use std::fs::File;
use std::path::Path;

/*
    BufRead isn't explicitly mentioned anywhere in the file, why do we need it imported?
    Additionally, if it's not included, then `lines` method throws an error
    I see that this has something to do with traits...
    Reading: https://doc.rust-lang.org/book/ch10-00-generics.html
    - Traits are similar to interfaces in other languages, but there are some differences - according to Rust Book
    - They can have default implementations
    - Traits can be parameters (any object that implement's that trait can be passed as a paramter)

    BufReader implements the BufRead trait:
    - https://doc.rust-lang.org/std/io/struct.BufReader.html#impl-BufReader%3CR%3E
    - https://doc.rust-lang.org/src/std/io/buffered/bufreader.rs.html#55-96

    Why do traits need to be imported in Rust?
    - https://stackoverflow.com/questions/25273816/why-do-i-need-to-import-a-trait-to-use-the-methods-it-defines-for-a-type
*/
use std::io::{BufRead, BufReader};

fn main() -> Result<(), std::io::Error> {
    /*
    What is the '?' operator in Rust?
    - https://stackoverflow.com/questions/42917566/what-is-this-question-mark-operator-about
    - https://www.becomebetterprogrammer.com/rust-question-mark-operator/#:~:text=operator%20in%20Rust%20is%20used,or%20Option%20in%20a%20function.
    */

    /*
    Related: Exceptions in Rust
    https://doc.rust-lang.org/book/ch09-00-error-handling.html

    Two types of errors: recoverable and unrecoverable.
    - Recoverable errors are where you just want to report the error to the user and retry the operation
    - Unrecoverable errors are things like accessing beyond the end of an arraya and you immediately want to stop the program

    For recoverable errors, there is type Result<T, E> and panic! macro for non-recoverable errors
    Result is a type that represents either success or failure: https://doc.rust-lang.org/std/result/enum.Result.html#:~:text=Result%20is%20a%20type%20that,the%20module%20documentation%20for%20details.
    T contains the success type and E contains the Error type.
    enum Result<T, E> {
        Ok(T),
        Err(E),
    }
    When handling the Result<T, E> return type, it's common to use the match keyword like so:

    match result {
        Ok(success) => success,
        Err(error) => panic!(error),
    }

    For unrecoverable errors, there is type panic!. You can either call this explicitly in the code, or by doing something bad like accessing the end of an array.
    You can have Rust show a stack trace of the panic by setting an environment variable RUST_BACKTRACE = 1
    */

    let path = Path::new("day1.txt");
    let file = File::open(&path)?;
    let reader = BufReader::new(file);

    let mut curr_sum = 0;
    let mut heap = BinaryHeap::<i32>::new();
    /*
    lines() is a function implemented by the BufRead trait. BufReader implements the BufRead trait.
    */
    for line_result in reader.lines() {
        /*
        Calling a function on an object changes the object's ownership
        Assignment leads to ownership and re-assignment also moves ownership, read here to understand why: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#ways-variables-and-data-interact-move
        However, if a value implements the Copy trait, then the value is copied

        https://depth-first.com/articles/2020/01/27/rust-ownership-by-example/
        String ownership examples: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html#the-string-type
        */

        /*
        References:
        If you don't want to copy, but you also don't want to change ownership, you can borrow with the & character
        If a function takes in a reference, the function doesn't own the value so it won't drop it upon completion of the function's execution
        References are immutable by default, but adding the `mut` keyword makes it so that the reference is modifiable.
        There is one big caveat which is that if you have a mutable reference to a value, you can't have any other references to that value.


        https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
        */

        let line = line_result?;

        /*
        Why can we call this function but still access `line` in the else statement?
        Because .len takes in a string reference, so line is borrowed, not moved.
        Ownership does not change as a result
        */
        let is_new_line = line.len() == 0;
        if is_new_line {
            /*
                What is the ! operator on println and why don't I need to import println via use?
            */
            heap.push(curr_sum);
            curr_sum = 0;
        } else {
            /*
            What is unwrap? Give me the result and if there's an error, then panic. It's somewhat equivalent to:
            match line {
                Ok(line) => line,
                Err(e) => panic!("Error"),
            }
            */
            curr_sum += line.parse::<i32>().unwrap();
        }
    }
    const TOP_K_ELVES: i32 = 3;
    let mut total_calories = 0;
    for _ in 0..TOP_K_ELVES {
        total_calories += heap.pop().unwrap();
    }
    println!(
        "{:?} total calories were retrieved by the top {:?} elves",
        total_calories, TOP_K_ELVES,
    );
    Ok(())
}

```

I've included comments in various sections describing some of the things I learned and was curious about.

Let's walk through the code, highlighting the interesting parts.



## Main function
Line 24 declares the main function. This is the entry point to a Rust program. The keywords after `->` indicates the return value of the function. In this case, the return value is of type `Result`. It looks like this:

```rust
enum Result<T, E> {
   Ok(T),
   Err(E),
}
```

This is a very common type in Rust that you'll see everywhere. I'll explain why in a following section.

In the `Result` enum, `Ok` implies the success response and `Err` implies the Error type. In our case, the success return type is `()` which is known as the `unit` primitive: https://doc.rust-lang.org/std/primitive.unit.html. It's similar to the `void` type in other languages. The error type is of type `std::io::Error`.

## Reading A File
To solve the challenge, we must first read in a file of information. I downloaded the file and saved it in my repo under `day1.txt`. The next step is to use Rust to read the file.

I use the `std::path::Path` module in the Rust standard library to do this. To do that, I first reference the module as a `use` declaration above to tell the compiler that I'm going to be using symbols from that module. The `Path::new` function returns a reference to a path object that can be passed into `File::open` for reading. I'll talk about references in a different post.

Next, I pass in the `Path` reference to the `File::open` function which, if you look at the [docs](https://doc.rust-lang.org/std/fs/struct.File.html#method.open), returns a `std::io::Result` type. This type is just a [shortcut](https://doc.rust-lang.org/std/io/type.Result.html) for the [`std::result::Result` type](https://doc.rust-lang.org/std/result/enum.Result.html) that we saw earlier in the `main` function - it's a very common return type in Rust.

### ? Operator
The interesting thing about this line of code is the ? mark operator at the end of the `File::open` function call. What does it do?

The `?` operator is specific to `Result` or `Option` function return types. It causes the function to return with the error if there is any error in the `File::open` call. If there isn't an error, in this case, it'll return the success type of the `Result` enum which is a `File` object.

When used on the `Result` type, it operates similarly to the following code:

```rust
let file = match File::open(&path) {
    Ok(file) => file,
    Error(error) => return Error(),
}
```

This code returns a `File` object if `File::open` succeeds. If there is an error in it, then the `main` function returns with the `std::io::Error` that `File::open` may throw. When we use the `?` operator, the function must return a `Result` with that `Error` type.

## Reading Line-By-Line
Now that we have the plumbing to read the file, let's go through it line-by-line to solve the challenge. We will use the `BufReader` `struct` to do this. The `struct` implements a trait called `BufRead` and this trait has a method `lines()` which we can use to iterate through each line of the file. A trait in Rust is similar to interfaces in other languages. The interesting thing is that we need to call `use std::io::BufRead` to tell the compiler that we want to use this trait. Otherwise, it'll throw an error saying that the `lines()` method does not exist on the struct `BufReader`.

The `reader.lines()` call returns a `Result<String, std::io::Error>` type. To get the `String` value out of the result, we can, just like we did previously, use the `?` operator. Note that the error type in `Result<String, std::io::Error>` is the same as the error type when we used the `?` operator on the `File::open` call which is why we can use the `?` operator here as well.

Now that we're able to read each line, we implement the logic to solve the challenge. I'm not going to talk too much about the logic as this post is intended to talk about Rust, not the challenge.

## Converting a String to an i32
`line.parse::<i32>()` is a function you can use to convert a String to an `i32`. Its return type is `Result<i32, ParseIntError>`. I initially tried to pull the `i32` out of the `Result` return type using the `?` operator, but the compiler wouldn't let me. The reason is because, when using the `?` operator, upon an error of the `parse` method, the `main` function would return with error `ParseIntError`. Right now, the `main` function is returning an error of type `std::io::Error` which is a different type from `ParseIntError`.

So, what is another way that we can pull the `i32` out of the `Result<i32, ParseIntError>` type? Similar to what we discussed earlier, we can use the `match` keyword like so:

```rust
curr_sum += match line.parse::<i32>() {
    Ok(val) => val,
    Err(e) => panic!("Error trying to turn the string to an int"),
}
```

This snippet will return the `i32` if it's able to convert the `String` to an `i32`. If not, it will `panic!`. What is a `panic`? A `panic` is an unrecoverable error that will cause the program to exit immediatly.A short-hand of the above `match` snippet is to use the `unwrap` method.

## Conclusion

These are some of the interesting things I learned about Rust in the first challenge in Advent Of Code. I also learned a bunch about ownership, references, and borrowing which I'll save for a different post since those are much longer topics.