I’ve always wanted to do the Advent of Code 2020 puzzles as a way to wind down in the run up to Christmas, but never quite got my act in gear to do it.

This year I’ve changed that, and I’ve pushed all my solutions to my AOC 2020 repository on GitHub. I thought that now that I’ve completed the challenge, I’d do a review mostly of how I used Rust to complete the challenges and what I found.

Rust Lessons Learned

Iterators

I really got more comfortable with iterator’s and began to reach for map, fold, filter, etc. the more I went into the puzzles. To begin with I stuck to what effectively was a ‘safe’ C-like coding experience - using mutable variables and loops. The more I went into the challenge I began to use more Rust things.

The one major downside I did find to iterators is that if they go wrong, you start to want to debug the body of them and then the closure can become frustrating. You either introduce a scope into the closure and bung the stuff in there, or have a seperate method instead of a closure to aid in debugging. Even still, I did feel many times that having a dumb C-like loop with mutable variables ended up being a nicer experience when things went wrong.

Shadowing Isn’t Always Evil

I’ve always felt variable shadowing was an evil that should be stopped. Lets take C for example here:

int foo(int a) {
  if (a < 42) {
    int a = 13;
  }

  return a;
}

I really dislike in C that it lets you shadow a local variable with a previously accessible variable - it just introduces the kind of problems like you see above where the user probably wanted to return 13 if a < 42, but they declared a new variable instead. In C-like languages I like to force shadowing to be an error as a result.

I came into Rust with this same view - shadowing is clearly bad language design! Except that Rust is uniquely placed to make shadowing sometimes nice:

fn foo(a: i32) -> i32 {
  let mut a = a;

  if a < 42 {
    a = 13;
  }

  let a = a;

  a
}

In this example we can declare a variable mutable, shadow it with another variable, and then assign the original variable to the new variable. All in a single step! This lets us temporarily make the a we are using mutable to do things with it. What’s even cooler is we can then do the same in reverse after we have decided a should no longer be mutable.

Before the Rust pedants come out the woodwork, the above should really be something more like:

fn foo(a: i32) -> i32 {
  let a = if a < 42 {
    13
  } else {
    a
  }

  a
}

So we can avoid making a mutable at all (but I couldn’t evangelise my point without the much uglier code before!).

It’s Easy to Use Crates (maybe too easy…)

Using crates is ridiculously easy. The number of times I added a crate just to try solving a problem using it, realised I didn’t need it so I then removed it, and moved on with my solution - was insane. I think that coming from C/C++ languages where I am most fluent has made me supremely dependency adverse, but with Rust it’s so trivial to just try a crate that you get sucked in.

I still think its a useful life skill to always attempt to avoid a dependency where you might not need one, but if you are to use dependencies then Rust is definitely what you want to be using for sure.

Crates Used

The exec_time crate

For Day 1: Report Repair, I started by using the exec_time crate, which I use throughout my solutions. The crate just prints the time taken to execute whatever method the included macro is placed on:

#[exec_time]
fn run_something() {}

Which prints the following to the command line:

Time run_something: 0 mills

The crate was a useful enough way to just dump the time taken in the function, but there is some bug with mutable arguments that causes the macro to fail. I worked around this by never passing mutable arguments, but its not a viable crate to use to be honest.

The colour crate

I also used colour crate in my first solution and all the solutions thereafter. It just lets you print to the command line with colours, with an easy API:

red_ln!("RED!");
green_ln!("GREEN!");

I did this solely for festive reasons - Christmas colours are green, red, and white - this meant that I printed each part 1 solution in red, each part 2 solution in green, and the exec_time crate above prints its timing info in white text interpersed throughout - very festive!

The intbits crate

For Day 3: Toboggan Trajectory, I used the intbits as part of this puzzle solution. The crate just lets you access bits in an integer with a nice API:

let mut i = 0;
i.set_bit(4, true);
assert!(i.bit(4));

The bit_set crate

For Day 5: Binary Boarding, I wanted to use an N-wide bitset for this challenge, to keep data used to a minimum again, and I went with the bit_set crate:

let mut seats = BitSet::with_capacity(1000);
seats.insert(4);
assert!(seats.contains(4));

The itertools crate

For Day 7: Handy Haversacks, I used the itertools crate which has a really nice way to get tuples out of an iter:

let (lhs, rhs) = line.splitn(2, "some seperator").next_tuple().unwrap();

I ended up using this crate everywhere - so that I could use the next_tuple method on the result of splitn to get a known tuple from the result. It was very much overkill for what I actually needed, but super useful.

The num-integer crate

For Day 13: Shuttle Search I used the num-integer crate solely to get access to the least common multiplier (lcm) method:

assert_eq!(lcm(3, 5), 15);

This was probably overkill (bringing in the whole crate), but I wanted to use Rust in all its glory as much as possible.

The regex crate

I’ve been told Advent of Code wouldn’t be complete without using some form of regex, and I hadn’t actually used regex at all in Rust before. I used the regex crate to accomplish what I needed, and while I’m no further forward in being better with regex than I was before, I did find the implementation of it using Rust to be pleasant to use (especially when you need to extract multiple bits from some complicated string).

The bit_reverse crate

I used the bit_reverse crate for my 20th days solution - this was the problem I hated solving more than any other in this years Advent of Code, and I used the bit reverse functionality to quickly get the bits in an integer in the reverse order. This was a really messy solution to the problem and honestly I’m not that proud of it, but it did the job, my code ran pretty fast, and it served a purpose.

Conclusion

My major takeaway from Advent of Code is that it’s a really great way to level up your usage of a language you already know a little of.

Originally I was going to attempt to use the Zig language to solve the problems - but I knew nothing of Zig’s syntax and tooling, I found the docs a little confusing to figure out (I was trying to work out how to load a file into a buffer!), and I quickly realised that I just wouldn’t complete the Advent of Code challenge while learning an entire new language and trying to work out the solutions.

With Rust I had been experimenting with things already and knew enough to be dangerous, but not enough to feel comfortable with it. Advent of Code was tremendously useful for reinforcing a thin level of knowledge of a language with a more robust appreciation of its quirks.

With that - I’m calling time on 2020, tha bliadhna ùr mo chàraidean, and I’ll see you in the new year!