-
Notifications
You must be signed in to change notification settings - Fork 0
Unexpected Value Updates
When we try to print out our account
twice in a row, we get this very mysterious error message. This is where we have to start talking about Ownership, Borrowing, and Lifetimes.
These three systems are closely connected to where they are almost the same topic.
Understanding these three systems are challenging, but the benefit is once understood, you mastered 90% of the difficulty in Rust. Everything else is easy.
These three systems dramatically change how you write programs and how you write code, so to some extent you will have to throw away your existing programming knowledge.
Let's dive in!
Our end goal is to learn these 12 different rules that distill the essence of owning, borrowing and lifetimes.
Let's dive in!(again).
This blog post makes the argument that the goal of the ownership system in Rust is to limit the ways we can reference and change our data. That will be our working definition of ownership and the hope is that this limitation will reduce the number of bugs we put into our program and make our code easier to understand as well. So ownership limits the way we reference and change our data.
To really understand what ownership does for us, lets do a side project (in JavaScript)!
The JavaScript version of this program will have a very small bug in it, intentionally. It will be small but nasty and when I rewrite this program in Rust, adding that bug will be completely impossible, so we will not even be able to compile the program. We will quickly learn that the ownership system will save us from introducing a bug that can be easily introduced in another programming language.
Here is the sample program, this will model two different cars represented as an object called a mustang
and the second object will be another type of American car called camaro
. Both have a name property and an engine property and for each of them the value of the engine will be an object like this: { working: true }
.
So we are saying if car.name == 'Mustang'
change the car.engine.working
from true
to false
.
That's the entire program.
const engine = {
working: true
};
const mustang = {
name: 'Mustang',
engine: engine
};
const camaro = {
name: 'Camaro',
engine: engine
};
function checkEngine(car) {
if (car.name === 'Mustang') {
car.engine.working = false;
}
}
If you are an experienced JavaScript engineer you almost immediately can see the bug, if not, we will go over it.
If I print out mustang
:
engine: {working: true}name: "Mustang"[[Prototype]]: Object
I have an engine that starts out as working. If I print out my camaro
, I yet again have an engine that starts out as working:
{name: 'Camaro', engine: {…}}
engine
:
{working: true}
name
:
"Camaro"
[[Prototype]]
:
Object
Exactly what I want.
Now here is the bug, I will call checkEngine(mustang)
and I get no errors...okay.
Now lets print the mustang
again and recall that it initially had a working engine.
{name: 'Mustang', engine: {…}}
engine
:
{working: false}
name
:
"Mustang"
[[Prototype]]
:
Object
Now working
is changed to false
and thats good because we passed mustang
to checkEngine()
and we wanted to change it to false because its specifically a mustang, but now out of curiosity I will take a look at camaro.
camaro
{name: 'Camaro', engine: {…}}
engine
:
{working: false}
name
:
"Camaro"
[[Prototype]]
:
Object
Wait a minute, the camaro
working
property has been changed to false
, I did not expect that....that is the bug.
Clearly there is a bug in here, lets look at the next diagram and understand this bug, which is a trivial bug, but it will help us understand what ownership is doing for us.
So the real issue is we have our mustang
, we have our camaro
but we only made a single engine
object and so only a single engine
object exists in memory and so the mustang
and camaro
were referring to that same engine
object. So when we changed the data of mustang
from true to false that is going to change some data in the mustang but erroneously we are also changing data tied to camaro
as well and thats why it has working of false
. Its all because we shared the same data between two objects when we probably did not really mean to.
Let's continue this at The Goal of Ownership and Borrowing Wiki.
Here is Fix #1
So with the above we could allow each car to use the same engine
object but we would prevent that engine object from being updated. We can have the mustang
and camaro
object refer to the same engine
object as long as its read only. This may not be the best solution but it definitely would solve the problem.
Fix #2 would be more satisfying and more appropriate:
We could simply make two separate engines. This will not cause unexpected updates.
We now have two ways to solve the problem, but they are custom tailored to this example. What lesson can we learn to not make mistakes on future projects no matter what language I use? Are there any rules we can think of to apply to future projects without running into this bug again?
From Fix #1, the rule we can take away is:
We can enforce the rule to allow multiple things as long as it is read-only.
From Fix #2 the rule can be:
The lesson is subtle, we never get this bug, if we ensure the value can only be updated if there are no read-only references to it. As soon as we have another object that also references the same object, it introduces this bug.
The two above lessons form the basis of the ownership and borrowing system:
The goal of ownership and borrowing is to annihilate the possibility of seeing the above bug.
Lesson number one is tied to rule 3,5, and 6: