It feels like an eternity since I've started using Rust, and yet I remember
vividly what it felt like to bang my head against the borrow checker for the
first few times.

I'm definitely not alone in that, and there's been quite a few articles on
the subject! But I want to take some time to present the borrow checker from
the perspective of its benefits, rather than as an opponent to fend with.

Some folks get the impression that Rust comes with “manual memory management”,
but in my view, nothing could be further from the truth. My position is that Rust
comes with “declarative memory management” - and I'll show you why.

The Style Guide

Rather than reason about theory, let's build something concrete for this post.

Introducing Overbook, a fictional book publisher. Overbook has a style guide.

It's really simple: do not, EVER, use commas. Commas, Overbook says, is the sign
of lazy writing and they shall, quote, be eradicated.

According to the style guide: “She read then laughed” is fine, but “She read,
she laughed” isn't. Pretty simple stuff.

And yet, at Overbook, they keep seeing that rule broken by authors. It's
like they're not even trying! They used to check it by hand, but it was tedious.
Every time someone sent a new draft, they had to check the whole book again.

Since the business took off, manual style checking for books has become a big
bottleneck for them, and they're looking for an automated way to check that.
Some sort of program, maybe, possibly?

Meet Robin

Luckily, one of the employees, Robin, knows some programming. Unluckily, Robin
went to university and all they learned was C (and some Java, but the Java Runtime
won't install on Overbook's computers for some reason? weird).

So Robin sets out to write their checker in C - after all, a lot of great
software has been written in C, what could go wrong, right?

Manuscripts at Overbook are just .txt files, and they're always in plain
English, so Robin knows they don't have to worry about silly text
encodings, ever.

A writer once tried to sneak an emoji in a manuscript to Overbook.

Once.

“Only one TODO left, this is going better than expected!". Robin feels
pride. They knew to be careful about memory usage - the check() function
doesn't even allocate any memory! Robin goes back to work.

Further e-mails from management

Many thanks for the new version of the checker. We were able
to try it with many manuscripts at once - now that's efficiency!

However it appears something is wrong. It properly detects whether a
manuscript has a mistake or not. But it seems to print the wrong
section of it. Could you take a look?

“Oh no”, thought Robin. “This is why I don't code. It worked on my machine though?”

Robin starts making changes to the program left and right, trying desperately
to reproduce the bug management is talking about. Finally, they stumble upon it,
by changing the order of the paths in main():

“Well, there it is. That won't do!". Robin take some time to go through the
program's code, puzzled. As they return from a coffee break, it hits them:
“OF COURSE! All results point to the same buffer! The one buffer I use to
read all the files”.

Robin looks around for a colleague to explain this to, just to make sure
they've fully understood the source of the bug, but no one is quite equipped
to confirm or deny whether that is the case, so they keep thinking aloud.

“This means that.. all the errors will show excerpts from.. the last file
that was read! It all makes sense”.

Robin is now thinking of ways to fix it. They could use one buffer per file,
of course, but then memory usage would skyrocket. That's not a route they're
willing to go down.

This wouldn't happen if errors were reported as soon as they were found, but
it seems a shame to undo their last refactoring now. No, they'll have to find
something else.

“I suppose I could always copy the relevant section of text?” thinks Robin, and
so they do:

“Here we go again”. Robin opens up the program, and, visually scanning
its source, quickly figures it out. They're using malloc(), but never
calling free().

“Well, it'll get freed when the process exits.. I think?” (Robin seems to
remembers that Amos said something about that somewhere, but they can't find
it again, and besides - their boss would never be convinced by a random blog
post). “But let's fix it anyway”.

“Don't feel bad, it's an easy mistake to make. free() only frees a single allocation,
it's not recursive”. Robin thinks that, they know that, they just haven't written C
seriously for a while, and it's not even their job description, but, sure, Jeff, whatever.

“You have to be careful about ordering too. If you swap those free(s), you're sure to
get a segmentation fault”. Robin isn't quite sure a segmentation fault is what would
happen, and remembers that there are platforms that don't have segmentation faults,
but they're not about to tell that to Jeff, who seems way too pleased with himself.

~ Deep breath ~

“Thanks for all the help Jeff, see you around!”

One year later

Robin hasn't heard from management in a while. Over the course of the past year,
the checker has verified thousands of manuscripts - quickly, efficiently, and
without leaks.

But all good things come to an end, and, while in the middle of thinking about
the promotion campaign for Overbook's next big release, “Famous Comma Survivors”,
Robin is called urgently into a meeting.

“We're in deep shit Robin. And you know I hate to use this language”. Robin
isn't sure at first whether Tim is talking about C or.. oh, no, of course,
he doesn't like to swear. “What seems to be the problem?” “Nothing works anymore.
The checker is completely busted. It was fine just yesterday!".

The checker had become integral to Overbook's operation. Without it, no new releases
could be approved and business had come to a screeching halt. While it was busted,
the business was losing dozens of dollars every minute.

Robin takes a look. The program still looks the same.. sort of. All old code looks
kinda different after a while, doesn't it? But still, everything is into place.
It reads files, checks for commas, collects results, reports mistakes, and then,
most importantly, frees everything.

Then, a thought occurs. “Say, how many manuscripts are you checking at the same time?”
asks Robin. “Big volume this week, we've got 130 manuscripts under review”. “Ah.”

“Ah.". Robin looks for MAX_RESULTS, and, sure enough it's still set to 128:

“Oh that's not good. If we write past the end of results, we're.. corrupting the stack?”
thinks Robin. “Wait, no we're not, luckily results is at the top of the stack, and our
OS prevents against stack overflow”. Still, Robin realizes they came too close to a real
catastrophe. Had variables been declared in a different order, serious data corruption
might have occured, and they could've let some commas slip through, ruining Overbook's
reputation once and for all.

Since this is an emergency, Robin bumps MAX_RESULTS to 256, and business is back
to its usual pace.

“Thanks Robin. You're a lifesaver. It's situations like these that make me feel
somewhat bad about paying you minimum wage. Not bad enough to do anything about it.
But you know. Bad-ish.”

But Robin knows. They know the program isn't fixed. It's just going to break again
later. In fact, that's not the only hardcoded limit. What if a manuscript comes
in that's larger than 256 kibibytes?

“Ah! I see! It must be returning an Option. If it finds as match, it returns Some(index),
and if not, it just returns None”. Robin quickly skims the pattern matching chapter again,
and decides to use a match:

Robin is feeling better and better about this. No malloc! No memcpy! And
that “sub-slicing” syntax is so refreshing. They even thought to use {:?}
as a format string for slice, so that it's double-quoted.

There's no free, either. Robin has read in the book that memory was freed
as soon as it wasn't used anymore. So Robin knows that, after the match, s
is freed - because it isn't used anymore after that point.

The slice (&s[index..]) isn't an allocation, it's just a reference to a part
of s. They also know that the string "sample.txt" is stored directly in the
executable, which gets mapped to memory when it's launched? Robin's memory is
fuzzy on that part, they make a note to re-read about it later.

On the road to parity

Still, Robin misses the way the C checker was structured. Everything is just
inline now.

“Yes, yes! This is good”. Robin notes that, although the buffer (s) has an initial capacity
of 256 KiB, it will grow as needed, should the checker encounter a file that's larger.

They also make a note that the file reading code is almost clearer, as we
can clearly see which two operations can fail: opening the file, and then
reading it in its entirety.

They're delighted to see that use directives can be placed directly within
functions, since check() is the only one using File and the Read trait
anyway (which provides Read::read_to_string - different from the
std::fs::read_to_string we used before).

It's not quite equivalent yet, though. The C version had check return a CheckResult,
which could contain a Mistake. Robin now realizes that their CheckResult can
simply be expressed as Option in rust - either Some(x) or None.

So, Robin figures they just need a Mistake type. A simple struct. Simple.

“Oh no”, thinks Robin. “This is why I don't code. This is what The Others warned me about.”

But Robin is still residually pleased from all the elegant code from before, and goes back
to the Rust book.

The book seems frustrated. It doesn't quite know how to go about explaining this - although,
it is clear that the book itself understands lifetimes inside and out. After a few more searches,
Robin settles on the following solution:

struct Mistake<'a> {
location: &'a str,
}

And everything compiles again. Robin is too excited to move on to the next part to really
stop and think about it. They figure, if it's that important, surely it will come up again
later.

So, now, all that's left is to make check() return an Option<Mistake> instead of () (which
is Rust for “nothing”, more or less).

“Good. Now both s and Mistake refer to the same lifetime, 'a”. Robin chose
'a, the only lifetime name they've ever seen. They wonder if all lifetime names
start with a single quote - and make a note to verify that later.

“Oh, bother”. Robin looks again at the definition of Mistake - which, at this point,
seems fitting.

struct Mistake<'a> {
location: &'a str,
}

“Ah, of course!". Robin realizes that the reason they can use &'a str as location's type,
is because the lifetime was declared earlier, in struct Mistake<'a>. And even though lifetimes
are a new concept, Robin knows about that angle bracket syntax (<>) - it's the same as
for generics!

Robin also knows how to make a function generic, and deduces that they simply
have to add <'a> to check():

Robin tried to follow the thread. They couldn't “borrow ‘s’ as mutable” more than once.
Robin assumed that “borrowing” was creating a reference, and “as mutable” meant that it was
a &mut s, not a &s - and sure enough, there's a &mut s right there in the code.

Why does it need to be mutable again? Ah, right, because it's used as a buffer when
reading the whole file:

“Ah, this is because they all share a single buffer. Wait, wait, wait. Hold on. Is the
compiler telling me about the exact same bug that was in the C version? When the
program printed only the output from the latest document?”

Robin was right. Very, very right.

“Well, we can just pull the same trick” thought Robin. “Only I hope this time, it doesn't
involve memcpy”.

“First, I think we have to change up Mistake a bit”. Robin realized that, currently,
location was a reference to a string. But to get out of this little lifetime problem,
they needed to own up to their Mistake. I mean, they needed to have Mistake own the
location.

And what's the owned version of an &str? “Ah, String!” they thought, looking
at the buffer they were reading the file into.

Robin had been confused originally why there were sometimes &str and
sometimes String, but it was starting to make a lot more sense now.

struct Mistake<'a> {
// this is still static
path: &'static str,
// this is now owned
location: String,
}

“Okay”, thinks Robin. “To be fair, location was a reference to
part of s. It didn't have any data on its own”. Robin was mentally
picturing something roughly equivalent to this:

“What I need here, is for location to own its data, which would look more like this”:

Robin was pointing at an imaginary wall, as if they were giving a presentation.
Their colleagues were starting to be low-key worried, but as soon as they heard
the words “Rust” and “rewrite”, they nodded, understandingly, and went on
about their business.

“I'm not sure how to make it own its data though. I haven't allocated any
memory explicitly yet! Rust doesn't have malloc, does it?”

Robin declares the Rust version production-ready, and locks the old
C version in the basement, never to be found again.

Five years later

The Rust style guide checker has served Overbook well. Nobody has complained
about it in five years, which, as far as software goes, is admirable.

But all good things come to an end (whoa, déjà vu anyone?), and, under new
management, Robin is asked for one last change. This change would make the
style guide checker into the ultimate checker.

Dear Robin.

Your checker has served us well. But it has come to my attention
that reporting only the first error in a text generates a lot of
back and forth with the writers.

We've had a long and fruitful brainstorming session and decided
that it would be best if the program reported all the errors
in a given book.

“Well then.” Robin opened up their text editor again. “If it's the last
change… better get to it.”

“We're going to need a vector” said Robin gravely. After so many years
on the job, they had stopped pretending anyone listened to them, and
took up the habit of saying their thoughts aloud, while tackling particularly
difficult problems.

It hadn't been a problem, since, with all the profits, they had finally given
each employee their own office. All of Robin's friends, still suffering the
open spaces, were red with jealousy.

“We're for sure going to need a vector.”

struct Mistake {
path: &'static str,
locations: Vec<String>,
}

It wasn't that Robin didn't know about linked lists. Oh, they knew allllll
about them. But Robin also knew that the processors used by Overbook had
branch predictors, and large caches, and that, for a large class of
problems, simple linear data structures outperformed more complex ones.

“Well, find isn't going to cut it anymore. Or, I suppose it would, if I ran
it successively on smaller and smaller slices of s. But there has to be a
better way”

Robin re-opened the documentation for str, and found just what they were
looking for:

“Huh, I guess ifs are expression too. Neat!” Robin felt right back at home. Everything
still made sense. It's like they had never left.

Robin was glad they had took the time to read up on map and collect the
previous evening, in anticipation of the task at hand. They had a precise
understanding of the differences between iterators and vectors.

To test the new feature, Robin created a new file, sample4.txt, which
contained a longer excerpt of Jabberwocky:

'Twas brillig, and the slithy toves
Did gyre and gimble in the wabe:
All mimsy were the borogoves,
And the mome raths outgrabe.
'Beware the Jabberwock, my son!
The jaws that bite, the claws that catch!
Beware the Jubjub bird, and shun
The frumious Bandersnatch!'

Upon closer inspection, it occurred to Robin that Lewis Carroll might as
well have been writing about upper management, but they wouldn't let
that thought distract them for the moment.

After changing main() to only check for sample4.txt, Robin ran the
program again:

“It's good, but it's not great. If this is going to be my last change, I'm going
out with a bang”, Robin decided.

“The best course of action would probably be to show line numbers, and highlight
where in the line the error was found. Also, that slicing in check() seemed kinda iffy.
It seemed like it belonged report() instead.”

Finally, the thought occurred that, since Caroll had used so many commas, the Strings
generated from check() were almost as large as the original text!

The plan

Robin started strategizing.

They wanted check() to be only responsible for checking, and report() to format
all the mistakes properly. They also wanted the errors to be positions in the
original text, not owned strings.

Robin didn't feel great about using a bunch of usizes rather than
references, but they reassured themselves by thinking that, well, they were
kept right next to the text they referred to, and that those fields weren't
even public - so no other code could mess with it.

“Since every manuscript has its own buffer now, I guess I can get rid of the global
buffer”, Robin thought as they removed a bunch of code from main().

“And now for the final touch.” Robin still wanted to highlight where the comma
was, using the ^ character, much like rustc did for them in the past.

Just as before, Robin only had to change the Display implementation, since
that's where all the formatting was done now. The position of the comma in the line
was simply the position of the comma in the text, minus the position of the start of
the line.

Robin knew to be careful to add 11 extra spaces, to account for the 8 characters they
reserved for the line number, and the nice " | " they added to separate line numbers
from the actual lines.

“85 lines (counting blank lines). That's not bad” thought Robin, as they turned
in the new version.

Epilogue

Although the new and improved version of the style guide checker was a huge success,
Robin never got a promotion.

Instead, Robin left and founded their own company. It had nothing to do with books,
but it did use Rust. Now and then, Robin would encounter a comment that referred
to the Rust style as “manual memory management”, and they couldn't figure out why
anyone would said that.

Robin, in their whole career, never had to manually allocate or free memory
when writing Rust code. (They were lucky enough to never interface with C code).

They simply declared what they wanted to happen, entering into a negotiation with
the borrow checker, and then revised their design until both parties were left
satisfied.

It was a partnership. Robin thought of the borrow checker not as a foe, but
as a colleague. Robin didn't think of Rust as “manual memory management”, but as
declarative memory management.