- In the previous section,
we introduced the concept of borrowing,
where you take a reference to an object
and then access a variable
without actually claiming ownership of it.
Now, the Rust compiler
has something called the borrow checker,
which enforces some strict rules
about what you're allowed to do
and what you're not allowed to do
when it comes to borrowing.
The borrow checker is quite frustrating initially.
It'll stop you writing the kind of code that you want.
But the reason it stops you is
because it's potentially dangerous.
It's in your best interests
that it doesn't let you make mistakes.
Okay, so what it boils down to is that the borrow checker
in the compiler will enforce these rules.
You're only allowed to have one single mutable reference.
Once you've borrowed an object mutably,
you can't borrow it again, okay?
You are only allowed to have one single writer.
You can have as many readers as you want.
You can have many immutable borrowers.
When you borrow immutably, you're not changing the object.
So it's safe to have many immutable borrows,
but when you take a mutable borrow,
that's potentially dangerous.
It doesn't let you have more than one
in case they interfered with each other.
So let's take some examples
to see how this works in practice.
First of all,
you can have many immutable references to a value.
Because immutable references can't modify the value,
this is guaranteed to be safe.
They're not going to interfere with each other.
Have a look at this example.
I've got a string.
Yeah, let's see, here's my string s,
and it contains the text, hello.
I've declared r1, r2,
and r3 are immutable borrows.
There's no mut keyword, so they're all immutable.
So r1 is an immutable reference,
r2 is an immutable reference,
and r3 is an immutable reference.
So if I printed out r1, r2, r3,
in each case, it'll print the underline string, hello.
So let's see an example of this before we go any further.
This is my demo project for the lesson, lesson07, borrowing.
In the main file, I'm going to un-comment the demo
for this part of the lesson, demo_borrow_checker,
and let's have a look at that code.
So here I've got three separate functions
to illustrate first of all, the fact
that you can define many immutable references,
and then we're gonna see some restrictions
about what you can't do.
So let's first of all, un-comment the first call,
defining_many_immutable_references we'll call that function.
And these other functions,
I'll just comment those out for now.
Let's have a look.
So this is a simple example.
Here's my string, hello.
r1, r2, r3, concurrent immutable references,
that's fine.
If I print out r1, r2, r3, obviously,
it's gonna print the underlying string,
which is going to be, hello.
Let's just run that to confirm, cargo run.
And it prints r1, r2, r3, and in each case,
it's gonna print the underline string, hello, okay?
So that's actually fine.
You can have as many immutable references
as you want to a variable, okay?
Now let's look at some restrictions.
So first of all, let's see what happens,
imagine that you've defined a mutable reference.
So let's say we've got a string and it's mutable,
so that its value could change potentially.
And let's say I've got a mutable reference, r1,
which could change the underlying string
via the mutable reference.
Basically, to cut a long story short,
once you've declared a mutable reference,
you can't have any other reference at all.
r1 is allowed to modify the underlying value, okay?
So via r1, this value could change.
So given that, you can't define another mutable reference.
You wouldn't be able to define another mutable reference r2.
You're not allowed to have more than one mutable reference.
And the reason for that is
because r2 might modify the underlying value.
Via r2, I might modify the string here,
and that could interfere with what r1 sees.
r1 and r2 might get in each other's way.
They might be making simultaneous updates
to the same string, for example, and that's chaos.
So you can't have more
than one mutable reference like that.
You can't even have an immutable reference, r3.
So imagine r3 was an immutable reference.
It wasn't gonna change the value, okay?
But that's not allowed.
Once you've got a mutable reference,
you're not then allowed
to have an immutable reference either,
because via r1,
I could change the underlying value here, okay?
And that might interfere with what r3 sees.
r3 thought it had an immutable string,
and suddenly the value seems to have changed
under the covers through some other mechanism.
So to summarize,
once you've set up a mutable reference, that's it.
You're not allowed to have a mutable reference,
another one, or an immutable reference,
just one writer at a time.
So that's worth an example as well, I think.
Let's go back into my demo.
And now I'm going to un-comment the second function call,
restrictions_after_defining_mutable_reference.
Right, here it is.
Here's my code, and let me just tidy up here.
So I've got a mutable string, which I change.
I set up a mutable reference.
That's fine, so far so good.
But once you've got a mutable reference,
you can't then have another mutable reference
or an immutable reference.
So if I try to un-comment those statements,
I'm gonna get an error on lines 33 and 34.
Let's just show you.
I'll do a cargo check.
And, right.
So yes, here's the first error.
It says, right, first of all,
because you've borrowed mutably, r1 is a mutable borrow,
then you can't have a second mutable borrow.
It's complaining about this attempt
to have a second mutable borrow.
Also, there's more.
Again, because r1 is a mutable reference,
then you can't have an immutable reference either, okay?
So this statement also fails.
So I'm gonna comment those lines back out again.
What's surprising though is that even this statement fails.
Let me explain what's going on here.
So I have my string and I've changed it a bit.
I've got a mutable reference, okay?
So once you've got a mutable reference,
you can't have any other references at all.
But unfortunately, the way the println macro works,
when you call it, it tries to borrow immutably.
It tries to do an immutable borrow of the string.
Well, we've already seen you can't do that.
If you've borrowed it mutably,
then you can't borrow it immutably afterwards, okay?
So that's also going to give me an error.
Let me show you that error.
So line 37, yes, indeed.
It says, but first of all,
because you borrowed it, the string mutable here,
so somebody is potentially changing it,
then you can't have an immutable borrower here.
So to understand this, you need to know
that println tries to immutably borrow
to use the string, and it can't.
Basically means you can't use the string.
If you want to use the string, you can't.
You've gotta use the reference instead.
You could use the reference
to change the string and to print it.
That would work, okay?
So the string that was originally huey,
and then became huey louie, via the reference,
it now becomes huey louie dewey.
So that would actually work.
If I ran that code now, that would work.
So let's just run it, cargo run.
And it's working fine now.
Okay, so just to summarize,
once you've taken a mutable borrow,
you can't have any other borrowers at all.
It's very tight and quite annoying, actually.
I mean, I've gotta say, when I started learning Rust,
I fought against the borrow checker constantly,
and you're never gonna win.
You just have to be careful and follow the rules.
It'll stop you from doing damage in your code.
Right, well, there's another part of the demo
that we're gonna have to look at,
restrictions after defining an immutable reference.
We've just been looking
at the restrictions after defining a mutable reference.
Now we're going to look
at the restrictions after defining an immutable reference.
So restrictions after defining an immutable reference,
easy for you to say.
So imagine I've got a string,
which kind of looks like this, is probably gonna be, hello.
And I've got an immutable reference, r1, okay?
So r1 isn't going to change the string.
r1 isn't allowed to modify the underlying value.
Obviously, because it's immutable.
You can't define a mutable reference, r2.
I'm assuming that the string itself is mutable.
So in theory, the string could change.
I've already got an immutable reference to it.
I can't define a mutable reference, r2,
because r2 might then change the string,
and that would confuse r1.
r1 thought it was basically constant.
If r2 was allowed to change it,
that would be problematic.
If r2 changed the underlying value
of the string, hello world,
then the value of r1 would also seem to change.
But r1 thought it was immutable.
So you can't do this.
Once you've defined an immutable reference,
you can't then define another mutable reference,
because it could change things unexpectedly.
Okay, so let's see an example of that.
Restrictions_after_defining_immutable_reference.
Okay, so I just tidy up my little area down here.
Let's go.
So first of all, the string itself is mutable, potentially,
so huey louie.
You can have as many immutable borrowers as you want.
Remember, having immutable borrows is fine.
So r1 and r2 are both basically pointing to the same string,
but you can't have an immutable borrow.
So this statement would give me an error.
I do a cargo check, on line 54, where it says,
because you've immutably borrowed,
and therefore you're thinking of it
as being like a constant string effectively,
you can't then have a mutable borrow,
because that would change the string.
The string that we thought was gonna be unchangeable
could be changed via r3, okay?
So you can't take a mutable reference to this string,
because it would interfere with your immutable view.
So that statement has to go.
Also, we can't modify the original object either, okay?
Because now that we've basically treating it as immutable,
it's gotta basically not change.
So you can't even do this.
Okay, when you say s.push_str,
what actually happens under the covers?
We'll talk about this later on.
It's kind of like an object oriented concept.
When you call a method on an object, like a string,
what it does effectively is it borrows the string,
it passes a reference to the string as the first parameter.
It's like a this parameter in Java or C++ or C#,
it's actually called a self pointer in Rust.
So when you call a method that's going to modify a string,
what it actually does, to be perfectly honest about it,
is it kind of does that.
The push_str function tries to borrow mutably,
take a mutable reference to the string,
so that it can mutate it, okay?
So this statement here is taking a mutable reference
to the string, and that doesn't work.
Just like this doesn't work.
Once the string has an immutable reference,
it's got to remain fixed.
You can't change it via a mutable reference
and you can't change it via this mutable reference either.
So line 57 is also gonna give an error.
Let's see.
Yes, so a mutable borrow occurs here.
It's quite subtle.
You don't see it, but underneath the surface,
this is effectively what's happening.
It is taking a mutable borrow, and you can't.
Once you have immutable references,
you can't then have mutable references
like this or like that.
So basically it's fixed.
Once you've got an immutable reference,
you can't then change it
whilst that immutable reference is alive.
Whilst this immutable reference is alive,
you can't change the string,
because it would change something
that we thought was immutable.
If these immutable references went out of scope,
then the restrictions are released.
The object is now modifiable
because the immutable references,
so the immutable references
are no longer constraining what we can do.
So anyway, so I've commented out this piece of dodgy code
and I've commented out that as well.
So we're left with immutable references,
which should actually work fine.
Let's just give it one last check.
Cargo run, and it's worked.
So huey and louie,
that's the string.
And when I look at r1 and r2,
r1 is huey louie, and r2 is also huey louie.
There we go.
So the borrower tracker is there for your benefit.
Basically, it means
you can have as many immutable references as you like,
but you can only have one mutable reference
and nothing else.