Advent of Rust 2: You Forgot the & Operator!

I’m half-surprised to find myself continuing on this half-baked plan to do a programming puzzle every day to learn Rust and write a stream-of-consciousness blog post detailing all the mistakes I make!

I’m not sure if I’ll keep up the blogging every day, especially because I hope that I’ll make fewer mistakes and wrong turns as the month goes on, and then it won’t be so interesting to blog about. But for now, here’s day 2 of Advent of Code 2020, in which we examine some bizarre password policies.

Day 2, Part 1

Remembering from yesterday how to create a new Rust project, I do cargo new puzzle2. Since the two parts of the puzzle were so similar yesterday, I’m assuming that’ll happen today as well. So I’m going to try to have one project for both parts of today’s puzzle. I’ll even use it as an excuse to learn how to handle command line arguments in Rust, so I can do cargo run 2 to get the second answer.

Today’s puzzle is to read lines from the input file, which look like 1-3 a: abcde. The part before the colon is the “password policy”, in this example meaning “the password must have between one and three a characters in it.” The part after the colon is the “password”, and the task is to check whether the password complies with the password policy. The answer to the puzzle is the total number of passwords in the file that are valid.

To get started, I’m going to take a look at the input file. First I download it and put it in the puzzle2 directory.

How the file looks will determine how I try to solve the problem. If the numbers are all only one digit, then I can process the lines just by character position; in other words, the minimum will always be at position 0, the maximum will always be at position 2, the required letter will always be at position 4, and the password will always be from position 7 until the end of the line. That’s very simple to do in most programming languages, so it should be the same in Rust. On the other hand, if the numbers can be more than one digit, then I will have to use something more complicated to extract them out of the string. In C, I might use sscanf(), and in JavaScript I might use a regular expression, or String.split().

I look at the file, and see that the numbers can be more than one digit, so I will have to take the more complicated approach.

Before I do anything else, I will copy the code that I wrote yesterday, to read the lines from a file. Then I should start by googling something, but I’m not sure what to google. Since I’m familiar with the sscanf() function in C, and I want to do something similar in Rust, maybe I should google “rust sscanf.” I do that, and land on a Stack Overflow question with a promising title: “Does Rust have anything like scanf?”

This tells me that a function like this is not built-in to Rust, but it nonetheless usefully tells me that there are several possible solutions! In order of upvotes, they are:

  • Write a macro to do it, that uses split() internally
  • Use the text_io package
  • Use the scan_fmt package
  • Use a regular expression

Now I have to decide which approach to take. I think I can eliminate the first option because writing a macro sounds more complicated than is needed for this task. I might use a regular expression if the two packages don’t look promising, but I do tend to think that regular expressions are overpowered for tasks like this one, and they are also hard to debug, especially if you have to come back to one several months after you wrote it. So I will first try to avoid regular expressions, meaning I have to choose whether to use the text_io package or the scan_fmt package. I click through the links and read the descriptions of the packages. They both seem like they would do the job, or at least I don’t know enough to say that one would be better than the other. But according to the download statistics at the bottom of the pages, scan_fmt was created more recently1 but is nonetheless about 6× as popular as text_io, so I’d guess I’m more likely to be able to get good help with scan_fmt.

It’s decided, I’ll use scan_fmt for this task! I add it to my Cargo.toml file (I remember how to do this from yesterday). as a first attempt, I cobble together the following code from reading the scan_fmt README file and the snippets in the Stack Overflow post:

#[macro_use]
extern crate scan_fmt;

fn main() {
    read_lines("input").map(parse_line);
};

fn parse_line(line: str) {
    let (min, max, letter, password) = scan_fmt!(line, "{d}-{d} {1}: {}", u32, u32, String, String);
    println!(
        "min:",
        min, "max:", max, "letter:", letter, "password:", password
    );
}

I get a lot of errors on this one! First of all I accidentally typed an extra semicolon in line 6, so I remove it. I also totally forgot how println!() worked from yesterday, and I thought I was using console.log() in JavaScript for a moment! I replace it with println!("min: {} max: {} letter: {} password: {}", min, max, letter, password);.

From reading the error messages, it also looks like I forgot to do error handling, so I add some unwrap() calls to the parsed variables: min.unwrap(), max.unwrap(), letter.unwrap(), password.unwrap(). (This is another thing that I learned yesterday. I didn’t quite remember what it did, so I went back to yesterday’s blog post to check.) That’s still not right, according to the compiler; it says that the result of scan_fmt!() has the type std::result::Result<(u32, u32, String, String), ScanError>, so maybe I need to unwrap() or expect() it before destructuring it? I do so, and the error vanishes, so I guess that was the problem.

The function parse_line now looks like this:

fn parse_line(line: str) {
    let (min, max, letter, password) =
        scan_fmt!(line, "{d}-{d} {1}: {}", u32, u32, String, String).expect("Bad format");
    println!(
        "min: {} max: {} letter: {} password: {}",
        min, max, letter, password
    );
}

It still doesn’t work though! I’ve solved all the compiler errors that I knew what to do with, but there are still some that are a mystery to me:

error[E0631]: type mismatch in function arguments
  --> src/main.rs:9:29
   |
9  |     read_lines("input").map(parse_line);
   |                             ^^^^^^^^^^ expected signature of `fn(std::io::Lines<BufReader<File>>) -> _`
...
12 | fn parse_line(line: str) {
   | ------------------------ found signature of `fn(str) -> _`

error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> src/main.rs:9:29
  |
9 |     read_lines("input").map(parse_line);
  |                             ^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `str`
  = note: all function arguments must have a statically known size

error[E0277]: the size for values of type `str` cannot be known at compilation time
  --> src/main.rs:12:15
   |
12 | fn parse_line(line: str) {
   |               ^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `str`
   = help: unsized locals are gated as an unstable feature
help: function arguments must have a statically known size, borrowed types always have a known size
   |
12 | fn parse_line(line: &str) {
   |                     ^

error[E0308]: mismatched types
  --> src/main.rs:13:50
   |
13 |     let (min, max, letter, password) = scan_fmt!(line, "{d}-{d} {1}: {}", u32, u32, String, String).expect("Bad format");
   |                                                  ^^^^
   |                                                  |
   |                                                  expected `&str`, found `str`
   |                                                  help: consider borrowing here: `&line`

The compiler gives two suggestions involving the & operator. I’m still not sure what that operator is for, but I’ll try following the suggestions. They were good suggestions, because that eliminates all but the first error!

Now that my terminal is not full of errors, I can more easily read that the compiler is telling me that the parse_line function is not the right type to pass to map(). I wonder whether I might need to change the signature of the function, but the signature that it’s telling me in the error message, just seems like… not what the function is supposed to do! I also wonder if I should not be using the map() method because my function is not returning a value, and maybe I need something more like JavaScript’s Array.forEach(). I google “rust iterator foreach” but don’t see anything that looks like it would solve my problem. Then I realize that I forgot to copy the expect("Bad file") from yesterday’s program, so maybe this is just another case of missing error handling? I’ll try fixing that first, before I try anything more complicated.

read_lines("input").expect("Bad file").map(parse_line); still gives me a type mismatch error, but a different one, that I understand better. I think I need to expect() each line individually before I pass it to parse_line(), so I again look at yesterday’s program and come up with map(|s| parse_line(s.expect("Bad line in file"))). That’s still an error, but the compiler has another helpful suggestion involving the & operator. (Clearly this & operator is important, because I keep leaving it out from places where it should be present. This is something that I should try to read about when I’m done with the exercise.)

Now it compiles, but there are still two problems: first of all, I get a warning, “unused Map that must be used”. Second, the program does nothing. I’d expect that even if there was a warning, it should still run correctly. But then I notice in the warning message, “note: iterators are lazy and do nothing unless consumed.” I think what happened is that because I didn’t run any methods on the result of map(), it never actually scanned the lines. So these two problems are actually the same problem.

I go back to googling “rust iterator foreach” and read a bit more. It looks like there was an RFC to add such a method, which was eventually closed. In that RFC I read that although there is no foreach() method in Rust iterators, you can use a for loop for that purpose. But also, the count() method is a quick hack that will consume the iterator. Although I generally don’t like quick hacks, I think I will go for the quick hack this time, for two reasons. One is that I believe I might be able to solve the rest of the puzzle using iterator methods, so it would be inconvenient to go from iterator methods to a for loop and then back to iterator methods. The other reason is that the eventual solution to the puzzle is actually a count of valid passwords, so ending the code with the count() method seems appropriate and I hope I’ll be able to use it later!

After adding count(), the program compiles and runs, but it panics at an expect() call because the scan_fmt!() call returns an error. Time for a coffee break!

After a break, coming back to look at this with fresh eyes, I wonder if the {1} in the format string (which I had intended to mean “read a string of at most 1 character”) was just me trying to be too clever for my own good, haha! The documentation for scan_fmt!() shows {2d} for at most 2 digits, but it doesn’t actually mention {2}. Sure enough, I remove the 1, and it works. I get a long list of lines such as: min: 3 max: 4 letter: l password: vdcv

Great! Now I want to create a struct (or class, or record, or whatever it is called in Rust) to hold each of these entries. I google “rust struct” and I land on another helpful-looking chapter of the Rust book, “Defining Structs”. I spend some time reading this page and learn a few things: the syntax for defining structs and creating instances of them, and I learn from the note down at the bottom that I should use String instead of str in structs because String is an “owned type” (I assume this means that it copies the string when it is created, and manages the memory itself, or in other words String is to str as std::string is to char* in C). It’s possible to use &str (there’s that & operator again!) but that requires using “lifetimes”, which according to this page is an advanced feature. Well, this is just more reinforcement that I’ll have to learn about the & operator soon, but for now I’ll do what the hint says and use String.

After reading that page, I’m able to write some code:

struct PasswordRule {
    min: u32,
    max: u32,
    letter: String,
    password: String,
}

fn main() {
    read_lines("input")
        .expect("Bad file")
        .map(|s| parse_line(&s.expect("Bad line in file")))
        .map(|rule| println!("{}", rule))
        .count();
}

fn parse_line(line: &str) {
    let (min, max, letter, password) =
        scan_fmt!(&line, "{d}-{d} {}: {}", u32, u32, String, String).expect("Bad format");
    PasswordRule {
        min,
        max,
        letter: String::from(letter),
        password: String::from(password),
    }
}

This almost works the first time! The compiler gives me two errors, but they both have good suggestions: I need to add the return type -> PasswordRule to parse_line(), and I can’t just print out PasswordRule with println!(). (I kind of expected that might not be possible, but I decided to try it anyway to see if it would work.) The return type is easy to add, and it has two suggestions for how to solve the second error: use {:?} instead (I remember wondering what this did yesterday) or implement the std::fmt::Display trait in PasswordRule.

i don’t know what a trait is, so I’ll try using {:?} first. This now tells me to implement a different trait, Debug, but it also tells me that I can add #[derive(Debug)] to my struct. I’ll try that and see what happens. It works! I have lots of lines that look like PasswordRule { min: 4, max: 5, letter: "c", password: "ccchc" }.

I’m still not sure what a trait is, but now I have a vague idea that it adds functionality to a type, and in this case I guess I was adding the functionality of printing a representation with println!()? Kind of like __repr__() in Python.

So now I have all the data in a data structure. This reminds me of the quote from The Mythical Man-Month2 by Fred Brooks:

Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious.3

It occurs to me that what I’ve done both yesterday and today is to get the data into the right form first, and then figure out how to solve the problem. And indeed at this point, the outline of a solution is starting to become clear: I’ll filter() the invalid passwords out of the iterator (assuming Rust iterators have a filter() method) and then count() the remaining items to get my answer.

A quick google of “rust filter iterator” shows that there is indeed a filter() method, and it does what I expect.

I wonder if I can add a method to the PasswordRule struct that tells if the password is valid? I start out to google “rust struct method” but before I do, I realize that the page about structs that I’m already looking at has a section on “Method Syntax”, and that looks like what I need. So I click on that instead and spend some time reading it.

Here’s what I come up with, after reading about how to write struct methods:

impl PasswordRule {
    fn is_valid(&self) -> bool {
        false
    }
}

fn main() {
    let count = read_lines("input")
        .expect("Bad file")
        .map(|s| parse_line(&s.expect("Bad line in file")))
        .filter(|rule| rule.is_valid())
        .count();
    println!("{}", count);
}

I want to check if this even works first, before I spend time writing the body of the method, so I make it always return false. When I run this program, I expect it to print the number zero, since it will think all the passwords are invalid. And it does!

So now for the body of is_valid(). Essentially what I want to do is count the number of occurrences of the letter in the password, and make sure it is not less than min and not more than max. I am pretty sure I would know how to do the second part, so I google “rust count character in string”, quickly realize that those search results will get confused with how to get the length of a string, and so google “rust count occurrences in string” instead. I land on a result from programming-idioms.org which gives me the concise s.matches(t).count(). That’s interesting, I got a useful result from Programming Idioms yesterday too, but two days ago I’d never heard of this site.

Here’s what I come up with:

fn is_valid(&self) -> bool {
    let count = self.password.matches(self.letter).count();
    count >= self.min && count <= self.max
}

Of course this doesn’t compile the first time. The compiler tells me that I should use the & operator (there it is again!) on self.letter. After I do that, it tells me that I’m doing an illegal comparison between count (of type usize) and self.min/self.max (of type u32). This is good to know; I just chose u32 kind of arbitrarily as the type for min and max, so I’ll change it to usize throughout. That did it and I got my answer!

Here’s the full code:

use std::fs;
use std::io::{self, BufRead};
use std::path;

#[macro_use]
extern crate scan_fmt;

#[derive(Debug)]
struct PasswordRule {
    min: usize,
    max: usize,
    letter: String,
    password: String,
}

impl PasswordRule {
    fn is_valid(&self) -> bool {
        let count = self.password.matches(&self.letter).count();
        count >= self.min && count <= self.max
    }
}

fn main() {
    let count = read_lines("input")
        .expect("Bad file")
        .map(|s| parse_line(&s.expect("Bad line in file")))
        .filter(|rule| rule.is_valid())
        .count();
    println!("{}", count);
}

fn parse_line(line: &str) -> PasswordRule {
    let (min, max, letter, password) =
        scan_fmt!(&line, "{d}-{d} {}: {}", usize, usize, String, String).expect("Bad format");
    PasswordRule {
        min,
        max,
        letter: String::from(letter),
        password: String::from(password),
    }
}

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<fs::File>>>
where
    P: AsRef<path::Path>,
{
    let file = fs::File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

Day 2, Part 2

Like yesterday, the second part of the puzzle involves processing the same input file but with different rules. Now we have to interpret the two numbers as positions in the password (starting from 1, not 0!) For the password to be valid, exactly one of those positions must contain the given letter, not both.

Before I start programming that, I want to figure out how to read command-line arguments in Rust, so that ideally I can just say cargo run 2 to get the answer to Part 2 of the puzzle and I don’t have to make a whole copy of my Part 1 code. Three guesses how I start out: but you only needed one, right? I google “rust command line arguments” and land here. This is a useful page and I spend a bit of time reading it.

Here’s my first attempt:

use std::env;

impl PasswordRule {
    fn is_valid(&self, part2) -> bool {
        if part2 {
            false
        } else {
            let count = self.password.matches(&self.letter).count();
            count >= self.min && count <= self.max
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut part2 = false;
    if args[1] == "2" {
        part2 = true;
    }
    let count = read_lines("input")
        .expect("Bad file")
        .map(|s| parse_line(&s.expect("Bad line in file")))
        .filter(|rule| rule.is_valid(day2))
        .count();
    println!("{}", count);
}

So if I do cargo run then I’d expect the same answer that I already got for Part 1, and if I do cargo run 2 I expect zero. That’s almost what happens: first the compiler tells me I need to add : bool to the part2 parameter of is_valid(). Once I do that, I do get zero if I do cargo run 2; but I get a panic if I do cargo run. But I think I understand why! Instead of accessing args[1] which may not exist, what I need is some method that will give me either args[1] if it exists, or a default value if it doesn’t.

So, I go back to the Vec documentation page that I was reading yesterday. But that doesn’t tell me if such a method exists. I google “rust vec checked access” and find a different Vec documentation page. (I am finding the Rust documentation really good, but it would certainly be better if there weren’t so many different pages on the same topic!) This tells me I should use the get() method which will check whether the index is within the bounds of the vector. It returns an Option and I think I even remember from yesterday that I can use the unwrap_or() method of Option to provide a default value! Here’s my attempt:

let arg = args.get(1).unwrap_or("1");
if arg == "2" {
    part2 = true;
}

This is almost correct but I get a type mismatch error, that I don’t understand at first:

error[E0308]: mismatched types
  --> src/main.rs:31:37
   |
31 |     let arg = args.get(1).unwrap_or("1");
   |                                     ^^^ expected struct `String`, found `str`
   |
   = note: expected reference `&String`
              found reference `&'static str`

But then I remember reading earlier about the difference between String and str. I’m not quite sure why I need to use the owned String type here, but I do remember how to convert str into String: String::from(). And I should almost expect at this point that I’m missing an & operator, but the compiler tells me so. (I also initially mistyped it as String.from(), but the compiler had a helpful message about that too.)

Unusually, this time, adding the & where the compiler says to add it, doesn’t make the program run correctly! I get this error:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:31:38
   |
31 |     let arg = args.get(1).unwrap_or(&String::from("1"));
   |                                      ^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
   |                                      |
   |                                      creates a temporary which is freed while still in use
32 |     if arg == "2" {
   |        --- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

I’m not entirely clear on what “borrow” means but I do have a vague idea of what is going on. But once I write it out in this log, it becomes much clearer to me: String::from(1) creates a temporary value, and it’s destructed after the unwrap_or() call, leaving arg as a dangling reference to the temporary object. So the compiler won’t allow it.

Here’s my attempt at solving this:

let default = String::from("1");
let arg = args.get(1).unwrap_or(&default);

I have a strong feeling that this is probably not idiomatic Rust, but for now it works as I expected it to.

Now to actually solve Part 2 of the problem. I remove the false from the if part2 statement in is_valid(), because this is where I’m going to put my code. What I want to do is, get the password character at index min − 1 and the character at index max − 1, and check that one but not both are equal to the letter. So I’m wondering:

  • Can I index a String using the [] operator in Rust?
  • If I can, then does that give me a length-1 String or some sort of character type?
  • Does Rust have a logical XOR (“one, but not both”) operator?

I google “rust xor operator” but then I realize from reading the synopsis of one of the results, that I can use the != operator on two booleans. I’m embarrassed, I had actually learned already that this is why you don’t really need a logical XOR operator, but it took a google search to remind me!

As for the answers to the other two questions, I think I’ll just test and find out whether it works. Here’s my first attempt at Part 2 code:

let first = self.password[self.min];
let second = self.password[self.max];
(first == letter) != (second == letter)

The compiler helpfully reminds me that I forgot self.letter, and it also tells me “String cannot be indexed by usize“. I guess that’s the answer I was looking for! So what can it be indexed by? I google “rust index string” and find this Stack Overflow answer which is actually very helpful! It tells me that strings in Rust are UTF-8, so if you are indexing position n you have to decide explicitly whether you want to have the n-th byte (which is fast, but may be only part of a character) or the n-th character (which is slower). I actually like this feature because when you’re using C it’s actually quite easy to forget that strings are byte arrays and so you write code that crashes as soon as your string contains an emoji.

I think in this case the passwords are all ASCII characters, so it doesn’t actually matter whether we index by bytes or by characters, but I’m pretty sure that indexing by characters is the right thing to do here. So now I try this:

let chars = self.password.chars();
let first = chars.nth(self.min).unwrap();
let second = chars.nth(self.max).unwrap();
(first == self.letter) != (second == self.letter)

And now the compiler gives me some errors which answer my second question as well:

error[E0277]: can't compare `char` with `String`
  --> src/main.rs:23:20
   |
23 |             (first == self.letter) != (second == self.letter)
   |                    ^^ no implementation for `char == String`
   |
   = help: the trait `PartialEq<String>` is not implemented for `char`

Indexing the string apparently does give you a character type, char. Now that I know that this char type exists, I wonder if it would be better to store the letter as a char in my PasswordRule struct, rather than a String. And that almost works! I have to add a let letter = String::from(self.letter); in the Part 1 code, because the matches() method does need a String, but I make some progress! Now this is the error that I get:

error[E0596]: cannot borrow `chars` as mutable, as it is not declared as mutable
  --> src/main.rs:21:25
   |
20 |             let chars = self.password.chars();
   |                 ----- help: consider changing this to be mutable: `mut chars`
21 |             let first = chars.nth(self.min).unwrap();
   |                         ^^^^^ cannot borrow as mutable

error[E0596]: cannot borrow `chars` as mutable, as it is not declared as mutable
  --> src/main.rs:22:26

I don’t understand why chars has to be mutable, since I’m only getting a character from the string, not changing it!

I consider doing what it says and making chars mutable, but I’m curious about why this is needed, so instead I browse the documentation for iterator methods. I have a vague idea that I might be able to make it work using the skip(), take(), next(), and last() methods, but I try a version of that and it doesn’t work right away, so I decide to give up on it and just go back to what I had, and make chars mutable. That works, but I get a panic on one of the unwrap()s because I indexed the string out of bounds!

Then I remember, the numbers in the file are 1-based, and indexing is 0-based, so I need to subtract 1 from each number. But I do that, and it still panics on that same line! Oh, but now I have a guess for why the iterator has to be mutable! My guess is that .nth(self.min - 1) exhausts that number of iterations from the iterator, so then when I do .nth(self.max - 1) it isn’t starting from 0 anymore, and it runs off the end of the string! In other words, nth() changes the internal state of the iterator, which is why it must be declared mutable, and that’s what the compiler was trying to tell me! (And if only I’d read the documentation of nth(), then I might have known that, haha!)

So instead, of saving self.password.chars() in a mutable variable, I just write it twice. I’m sure there’s probably a more efficient way to do this, but I do want to be done with the exercise at this point. And indeed, I get an answer which the Advent of Code website tells me is correct.

Here’s the full code, which I’ve also pushed to GitHub:

use std::env;
use std::fs;
use std::io::{self, BufRead};
use std::path;

#[macro_use]
extern crate scan_fmt;

#[derive(Debug)]
struct PasswordRule {
    min: usize,
    max: usize,
    letter: char,
    password: String,
}

impl PasswordRule {
    fn is_valid(&self, part2: bool) -> bool {
        if part2 {
            let first = self.password.chars().nth(self.min - 1).unwrap();
            let second = self.password.chars().nth(self.max - 1).unwrap();
            (first == self.letter) != (second == self.letter)
        } else {
            let letter = String::from(self.letter);
            let count = self.password.matches(&letter).count();
            count >= self.min && count <= self.max
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut part2 = false;
    let default = String::from("1");
    let arg = args.get(1).unwrap_or(&default);
    if arg == "2" {
        part2 = true;
    }
    let count = read_lines("input")
        .expect("Bad file")
        .map(|s| parse_line(&s.expect("Bad line in file")))
        .filter(|rule| rule.is_valid(part2))
        .count();
    println!("{}", count);
}

fn parse_line(line: &str) -> PasswordRule {
    let (min, max, letter, password) =
        scan_fmt!(&line, "{d}-{d} {}: {}", usize, usize, char, String).expect("Bad format");
    PasswordRule {
        min,
        max,
        letter,
        password: String::from(password),
    }
}

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<fs::File>>>
where
    P: AsRef<path::Path>,
{
    let file = fs::File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

Afterword

Compared to yesterday, it seems to be happening more today that I’m drawing on knowledge of other programming languages, where I see something in Rust and can say “hey, that looks like such-and-such in C++!”

I never consciously realized this before, but the approach of “show me your tables and the flowcharts will be obvious” is what I often reach for, and it’s been successful both yesterday and today.

It also occurred to me that it’s been really useful both yesterday and today to know what iterators are in other programming languages, and how to use them. Iterators seem to be so common in Rust, that if I was coming to learn Rust but hadn’t encountered iterators before, I’d have a really hard time.

Similarly, I think I also would have had a hard time if I hadn’t been familiar with the family of built-in types that C and C++ provide. It was easy enough for me to recognize that usize is probably the same as size_t, and u32 is probably the same as uint32_t, and that there is such a thing as a char type (there isn’t one in other languages such as JavaScript.)

Finally, it’s very clear that I need to learn what the & operator does! It seems like every time I write any code, the compiler tells me “add the & operator here, here, and here” and I just blindly do that. That’s not a good feeling! But at least it’s a good feeling that the compiler knows this stuff; I’d be totally lost if the error messages were less helpful.

I mentioned Julia Evans’ blog yesterday as an inspiration for this series of posts, and it so happens that she has a blog post about that topic! But that post might be too advanced for me at this point, because it talks about the audience being someone who has read the page in the Rust book on lifetimes. So instead, I think I will start by reading the “Understanding Ownership” page in the Rust book, before I start tomorrow’s puzzle.


[1] It actually was created earlier, but I only figured that out later, because the earlier versions were hidden behind an expander

[2] Which I totally haven’t read

[3] Where “flowcharts” is an old-fashioned word for “source code,” and “tables” is an old-fashioned word for “data structures”

1 thought on “Advent of Rust 2: You Forgot the & Operator!

  1. One alternative to `parse_line` (and maybe more idiomatic) would have been to implement the FromStr trait for your PasswordRule struct :

    “`rust
    impl FromStr for PasswordRule {
    type Err = ScanError;

    fn from_str(s: &str) -> Result {
    let (min, max, letter, password) =
    scan_fmt!(s, “{d}-{d} {}: {}”, usize, usize, char, String)?;

    Ok(Entry {
    min,
    max,
    letter,
    password,
    })
    }
    }
    “`

    And in the main function :

    “`rust
    let file = File::open(“./input”)?;
    let br = BufReader::new(file);

    let result = br
    .lines()
    .filter_map(Result::ok)
    .filter_map(|line| line.parse().ok())
    .filter(PasswordRule::is_valid)
    .count();
    “`

    Anyway, thanks for your blog articles, it’s a pleasure to read about your journey 😉

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.