My friend Malisa and I have been hacking on Servo this summer. We started from scratch, with no experience writing Rust, let alone compiled langauges. It's been challenging and fun! One of the trickier Rust concepts to understand for me was Rust's references and borrowing.

Ownership in Rust is in many ways like owning a coloring book. You can have all your friends come look at it together and flip through the pages. However, they can't color in it. This is an immutable borrow in Rust lingo.

But say you actually want to create a collective art piece with the coloring book. You can lend it to one friend at a time and tell them they can color however they want, or mutably borrow in Rust lingo. They have to give the book back to you when they're done, and you may lend it to the next friend, and so on0. If you tried to let two friends color at the same time, they can end up fighting to color the same spot.

These are the two rules of Rust's borrow system:

If it's mutably borrowed, it must be the only borrow that currently exists. Otherwise, many friends can borrow immutably at the same time.

In both cases, if the lender wants to color the book, then they must get it back from the borrowers first. How else would you color a book you don't have in your hands?!

I've found that making the compilation fail, and then reading the compiler's detailed messages is a great way to learn these new concepts. Steve Klabnik, the author of "The Book", wrote an awesome introduction to Rust's borrow system, so let's mess with one of its examples, break things, and learn!

Let's start with something simple that compiles:

fn main () { let mut x = 5 ; let mut y = x ; y += 1 ; println! ( "x is: {}" , x ); println! ( "y is: {}" , y ); }

warning: variable does not need to be mutable, #[warn(unused_mut)] on by default -- > <anon>:2:9 2 |> let mut x = 5 ; |> ^^^^^ x is: 5 y is: 6

The compiler helpfully warns that x didn't have to be mutable. But why? It's because y ends up being a separate copy of 5 , and x is never modified in the code. You can tell that x and y are two different things in the memory, because the printed values are different.

What if we didn't want to copy x into y , and wanted changes to y to apply to x ?

fn main () { let mut x = 5 ; let mut y = & x ; * y += 1 ; println! ( "y is: {}" , y ); }

error: cannot assign to immutable borrowed content `*y` --> <anon>:5:5 5 |> * y + = 1 ; |> ^^^^^^^ error: aborting due to previous error

What happened? This time, y is not a copy of x . Instead, y is a reference to x , and therefore, it had to be dereferenced before 1 is added to y . One important thing to note is that y is an immutable reference, even though y itself is mutable! That is why the compilation fails. y didn't have the permission to change the value, yet it tried to do so anyways.

Now that we know what's going on, let's fix this code and make the compiler happy again!

fn main () { let mut x = 5 ; let mut y = & mut x ; * y += 1 ; println! ( "y is: {}" , y ); }

y is: 6

y takes a mutable borrow of x , and it can change x 's value. So when we print y it successfully prints 6.

What happens if we try to print x instead? This brings us to one of the Book's examples.

fn main () { let mut x = 5 ; let mut y = & mut x ; * y += 1 ; println! ( "x is: {}" , x ); }

error: cannot borrow `x` as immutable because it is also borrowed as mutable [--explain E0502] --> <anon>:7:26 3 |> let mut y = &mut x ; |> - mutable borrow occurs here ... 7 |> println! ( "x is: {}" , x ) ; |> ^ immutable borrow occurs here 8 |> } |> - mutable borrow ends here <std macros> :2:27: 2:58: note: in this expansion of format_args! <std macros> :3:1: 3:54: note: in this expansion of print! ( defined in <std macros> ) <anon> :7:5: 7:29: note: in this expansion of println! ( defined in <std macros> ) error: aborting due to previous error

Compilation fails! The compiler gives a pretty good explanation. It says that println! cannot immutably borrow x because x is already mutably borrowed by y . This violates the first rule of Rust's borrow system: when something is mutably borrowed, there cannot be other borrows.

The Book shows a way to correct this code by using curly brackets to introduce a nested scope.

fn main () { let mut x = 5 ; { let y = & mut x ; * y += 1 ; } println! ( "x is: {}" , x ); }

x is: 6

The Book does a better job in explaining scopes than I can, so I highly recommend reading the Book about this section.

Let's break this code one last time.

fn main () { let mut x = 5 ; { let y = & mut x ; * y += 1 ; } println! ( "y is: {}" , y ); }

error: unresolved name `y`. Did you mean `x`? [--explain E0425] --> <anon>:7:26 7 |> println! ( "y is: {}" , y ) ; |> ^ <std macros> :2:27: 2:58: note: in this expansion of format_args! <std macros> :3:1: 3:54: note: in this expansion of print! ( defined in <std macros> ) <anon> :7:5: 7:29: note: in this expansion of println! ( defined in <std macros> ) error: aborting due to previous error

The compiler lets us know that y does not exist. This is because y is defined inside the inner scope delineated by the pair of curly brackets, and outside of them, y doesn't exist! Introducing inner scopes gets very handy when you want to define things only temporarily.

The Rust compiler does a great job of pointing out possible sources of error in the code. Compilation errors are not scary, and they're in fact a great conversation starter with the compiler. Try introducing different kinds of errors, and see how the compiler responds. From these conversations, I find myself learning new concepts that I didn't know before, and writing better code over time.

Now you should go break some code! Thanks for reading!

Special thanks to Nick Fitzgerald and Malisa Smith for providing feedback on drafts.