Advent of Code 2022: Learning Rust
Getting to the end of the year, you know it will be that time again: Advent of Code time! Starting on the first of December, up until Christmas, there will be a daily programming challenge. Last year I did an attempt to learn F#, which was not really successful. The combination of learning a new language and it being the first year I really participated in Advent of Code was just too much and after a few days I switched back to C#.
This year I wanted to skip it at all, but in November I decided to have a look at Rust and I got hooked. I looked for an IDE (more about that later), read a book and some documentation, did some of my favorite kata’s, and there it went wrong: I decided to solve some puzzles of Advent of Code 2021 and I decided to join this year’s edition again, with a goal: to learn Rust .
Editor of choice
When learning a new language, a few things are important, and having a good editor might be the most important one. I started with Emacs , as this is my go-to editor for a lot of my daily tasks. And though it worked, I missed the workflow of a real IDE, with autocompletion, quickly running tests and, if all else fails, debugging.
Just recently, JetBrains released Fleet , a lightweight editor. I installed it before but had not tried it yet, so I decided to set it up for Rust development. And, although it was easy to set up, I quickly realized it was unusable for me: there is no support for vim mode (yet).
So, for now, JetBrains IntelliJ is the big winner! Fast, easy, autocompletion and Rust documentation, all in one IDE. Being a long time Rider user, I am unfamiliar with the keybindings, but, for now, I just decided to use my mouse a bit more, learning the most important key bindings along the way.
I have used two more editors, for a funny but not completely unsuccessful experiment: Rust Compiler and Decoder, both Android applications. I actually solved a few puzzles on my mobile phone, while I was away with my family for the weekend. In just a few days, I got familiar enough with the language to solve two days with Rust Compiler and one with DCoder. The latter handles copying the input data much better,/ so I paid to get it ad-free. It still gives issues when using external libraries (crates) so it is not really useful for complex code.
The experience
It was for a reason I got hooked so quickly: for a C# developer, starting with Rust seems easy. On day 5 of the Advent of Code, I had the opportunity to work with a developer who started onboarding that same day. Just for fun, we started pairing on the puzzle of the day and solved it together. Although he never did Rust before, he was able to reason about it and even add a few lines of code himself!
The language is not that difficult to learn, but there are a few perks. I had some issues with borrowing and referencing , something many new Rust developers (or rustaceans) seem to struggle with in the beginning. I like the fact that variables are immutable unless otherwise specified! The general control of flow with if-statement, while loops and matching is easy, as is the more functional way of using Option<T> structures.
pub fn fizz_buzz(number: i8) -> String {
match fizz_buzz_test(number) {
Some(message) => return message,
_ => number.to_string()
}
}
fn fizz_buzz_test(number: i8) -> Option<String> {
if number %3 == 0 && number % 5 == 0 {
return Some(String::from("FizzBuzz"))
}
if number % 5 == 0 {
return Some(String::from("Buzz"))
}
if number %3 == 0 {
return Some(String::from("Fizz"))
}
None
}
Imperative vs. Declarative
In Rust, it is possible to write imperative code and declarative code. Just two examples for the same solution of the Advent Of Code, Day 1:
// IMPERATIVE
pub fn solve_part1_first(input: &str) -> i32 {
let mut strongest_elve = 0;
let mut current = 0;
for line in input.lines() {
if line.is_empty() {
if current > strongest_elve {
strongest_elve = current;
}
current = 0;
} else {
current += line.parse::<i32>().unwrap();
}
}
strongest_elve
}
// DECLARATIVE
fn get_elves(input: &str) -> Input {
input
.split("\n\n")
.map(|elf| elf.lines().filter_map(|cal| cal.parse::<u32>().ok()).sum())
.collect()
}
pub fn solve_part1(input: &str) -> u32 {
get_elves(input)
.iter()
.max()
.copied()
.unwrap()
}
Object Orientation
Just a small example to demonstrate how to write object-oriented code with Rust. The object Instruction gets a method ‘From’ so it can easily create an instruction from a simple line of text:
addx 2
addx 15
addx -11
addx 6
noop
#[derive(PartialEq)]
pub struct Instruction {
cycles: i8,
steps: i64
}
impl From<&str> for Instruction {
fn from(s: &str) -> Self {
match s.split_ascii_whitespace().collect::<Vec<&str>>().as_slice() {
["noop"] => Instruction { cycles: 1, steps: 0_i64 },
["addx", v] => Instruction { cycles: 2, steps: v.parse::<i64>().unwrap_or(0_i64) },
_ => unreachable!(),
}
}
}
fn parse_data(input: &str) -> Vec<Instruction> {
input
.lines()
.map(|line| Instruction::from(line))
.collect()
}
The documentation
Documentation is important when learning a new language. What different data types are available, how to control flow, etcetera. It takes some time to get used to documentation, but two things helped a lot: first, hoovering over keywords in JetBrains IntelliJ results in showing extra documentation with examples, which helped me a lot.
Next, the official Rust documentation is extensive, has many examples and links to an online playground to test the examples.
Conclusion
I find it hard to explain why I like rust. These past days I told some developers the same: I like the syntax as it feels very familiar to C#, and it is fast. When looking into the differences between C# and Rust, it’s about the large ecosystem (.Net) versus memory safety (Rust), and many other differences. Since I’ve only used it for Advent of Code, these differences are not that important. After the Advent of Code, I will try to do some small projects in Rust and see how that works out. Not much of a conclusion, but for learning Rust: mission accomplished!
Links