- So we just looked at our value semantic example,
but let's imagine that what we really wanted
is not the goroutine here operating
in its own sandbox and its own copy.
Let's say what that next data transformation,
what it really needed to do,
was manipulate the memory here,
which was gonna be in the frame above us.
Now what we need is pointer semantics,
and pointer semantics serve one purpose
and that is to share our piece of data
across a program boundary.
Sharing, if you don't need to share something,
then you don't need a pointer,
you don't need the pointer semantics,
and this is where the pointers are really going to come in.
Let's take a look at the mechanics around sharing
and then continue to talk about the benefits
and the costs of pointer semantics,
and we'll try to continue
to bring all this stuff together as we go.
So let's go back to our original program.
Let's come back here again.
We're gonna clear our stack right here,
here we are, and we've got our stack
and we're operating in main.
And main has its variable count,
and we can get the value of count,
we can get the address of count.
But this time on line 17 we've done something different.
I'm not passing the value of count
across my program boundary.
I want you to notice that what we're passing
is the address of count.
Now you might think that this is what's called
a pass by reference.
Lots of people say that this is a pass by reference.
It really isn't.
Remember pass by value means WYSIWYG.
What you see is what you get.
Pass by value means that we're making copies of data
as we cross over program boundaries.
This is a pass by value.
It just so happens that the data we are copying and passing
is not a value, it's an address.
And I need to stress again that addresses are data.
So since we're gonna make a copy of count's address,
and pass that across the program boundary,
we need to store an address.
Guess what?
This is what pointer variables are for.
Pointer variables are not special.
They aren't, they serve one purpose,
and that is to store addresses.
Our address data.
Now if you look at the declaration
of the parameters for increment,
you can see now that we've added this operator called star.
And the star operator is allowing now
us to declare the pointer variable.
I want you to understand that pointers
are literal types.
Remember when I said before in the other section
we have name types, we have literal types, unnamed types.
And pointers are literal types.
You can take any existing type, like name type int,
put the star in front of it,
and you've just now identified
the literal pointer type for that.
And what that means it's changing everything,
inc is no longer representing an integer,
inc is now representing the ability
to store an address.
That's what we're going to be doing.
And that address is going to point to something.
In this case, what we've done is we've
taken the address of count,
and we've passed that across the program boundary.
Which means that is what we're gonna be storing
in this box, that address.
Which now means that we're able to point to a piece of data
outside of our frame.
Pointers are for sharing.
Pointers are confusing because of that star.
The star dictates that this is now a pointer variable,
it's always four to eight bytes of memory,
depending upon the architecture, the size of an address,
and that is what we're storing.
But, this is very important.
You might think, well Bill,
If the star is what's dictating the variable's type,
which it is, why can't we just say star?
I mean it's the star that's dictating
this is a four-eight by value.
It's the star that's dictating this is a pointer variable
and what's gonna be stored.
You're right, except for one thing, it's not enough
to just store the address.
The whole reason for storing the address
is because we want to manipulate,
read and/or write the memory,
that that address is locating us to.
In this case, this count.
And I told you before, you cannot read and write to memory
unless you understand the memory's type.
You must understand the size
and the representation of this memory.
Type is life.
So, we can't just say star, you've gotta say star int.
What I'm saying is I want an address.
Absolutely, no pause, I need an address.
But guess what?
It can't just be any address,
it has to be and address to an integer,
because I wanna read and write integers.
And we're gonna get that type safetiness,
the compiler won't let me pass any address,
only addresses to values that are based on the integer type.
There we are.
And then what you see on line 28
is the use of the star operator again.
This can be confusing.
Remember the star is being used during type decoration,
now it's being using to say one thing.
Remember we're gonna put everything in English.
Use the variable by itself it's value of,
what's in the box.
You take ampersand variable, address of, where is the box?
And if you have a pointer variable,
then the star says, the value that the pointer points to.
And what we call this is an indirect memory read or write.
What you see on line 27
is an indirect memory read and write.
A read/modify/write operation through pointer indirection.
Remember I told you that the goroutine only has the ability
to directly read and write to memory within its frame.
Now when we made this call,
this is no longer the active frame.
This is now the active frame.
The goroutine is operating within this sandbox right here,
and now what we're able to do is say this,
the goroutine still performs the direct memory read.
Even though this is now still the active frame, right?
Remember again, this is our active frame, right here.
Our goroutine is operating within this sandbox right here.
And in order for this goroutine to be able to access
anything outside of its sandbox,
it still must perform a read of the address.
Remember that the goroutine
only has direct memory access to the frame.
If you want the goroutine to be able to access
memory outside of the frame, you must share the address
of that memory location to it,
so it can be done in a safe way.
And so now on line 28 what we have here
is an indirect memory read/modify/write over here.
And now what we're able to do
is modify that memory outside of our frame.
The pointer gives us this capability.
However, there's a huge cost to pointers.
Remember now we've just walked away
from our mutation isolation, our immutability.
What we've done is set ourselves up
for what we call side effects in our code.
When we start mutating memory,
we have to be very, very careful.
But if we're mutating memory
within the scope of value semantics,
we're good, we're clean,
we can't cause problems anywhere else except for ourselves.
But when we start mutating memory around pointer semantics
we have to be very careful.
Because at some point we are going to return
from this function.
We're going to move out of this program boundary,
and we're gonna move back into this program boundary.
Again, this being the active frame
this is what we're operating on.
And when we come back, we may not have realized
that the goroutine or some other goroutine
in a multi-threaded piece of software
has gone behind the scenes and has mutated this data.
It's not necessarily clean anymore, it's dirty.
And if we're not aware of that,
this can cause huge side effects.
And in a multi-threaded software it could cause data races.
Functional programming languages try to reduce
the side effects by not giving you pointer semantics at all.
Everything is value semantics.
But remember the cost of value semantic
is inefficiency with the data.
There are times where it's just more efficient,
simpler, to have one piece of data and that's it,
and share it across your program.
We're gonna continue to talk throughout the class
when to use value, and when to use pointer semantics,
I promise you.
Right now we're still very focused on mechanics
and some base semantics.
But this class is going to, over the course of time,
as we continue to go farther,
try to teach you when to use one semantic over the other.
There's just still more that I have to teach you.
And so as I run this program here,
you will see now that on the return
we've mutated the memory in the frame above us.
And now when we come back up, we now see that mutation.
This is technically a side effect.
We have mutated memory outside of our isolated space,
our safe space, and have mutated something out here.
This is gonna require a lot more care.
And you can see here that now that we're here it's all gone.
Now, there's one more thing I want to talk to you about
as it relates to what we've seen here.
I keep drawing for you the active frame.
One thing that's very important,
is I want you to come to the realization
that any memory below the active frame
is not valid memory.
None of this memory is valid.
If you noticed that we've been working the stack down,
now that is a silly implementation detail.
We could have done it any other way.
Just historically this is how it's done.
But I want you to understand that as we make function calls
we are going deeper into the stack.
As we return we are coming back up the stack.
There are stack and frame pointers that allow us
to identify where the active frame is.
And in this case, what I want us to understand is that
anything below the active frame
this memory is no longer valid.
It's no longer in play.
The active frame and memory above has always
got a level of integrity because we can continue to go up,
but going down causes problems.
Remember, we have the concept of zero value.
And zero value is a very powerful concept in Go.
Think about it, I'm back up in main right now.
We've done this mutation.
But what happens when main makes another function call?
When main makes the next function call,
it's going to need another frame.
And when it takes that frame it's going to clean it up.
This is why I wanna stress
that memory below the active frame
no longer has integrity because it's going to be reused.
So we wanna keep that idea as well as we continue
to learn about our value
and our pointer semantics in these stacks.