- Alright let's go explore a little bit deeper
the idea that we're passing concrete data
across these program boundaries
when we're using interface values.
We could call this, you know, like implicit interface
conversion, but again I really want to focus on the fact
that it's the concrete data that we're moving
and I want to look at more of the mechanics
behind it just to solidify this a little bit more.
So look I've got the mover interface,
the locker interface, that's move, lock, and unlock.
And then I've got the move locker,
remember the composition of multiple interfaces.
So a move locker knows how to move, lock, and unlock.
We've got the three interfaces right there.
Then I've defined a concrete type named bike.
Now this is implementing using the,
or declared using the empty struct, right?
This is a case where you might want a method based API
for some reason where there is no state, right?
Maybe we're using bike as a name space.
Gonna want to be very careful, remember functions
are always gonna be more precise than methods
but in this particular case since there's no state
then the API would have to be as precise
as any function, right?
And we've got the implementation of move, lock, and unlock.
So a bike is a mover a locker and a move locker.
Okay great, now this is where things get fun.
On line 49 and line 50
we're declaring two interface values
set to their zero value, there they are,
that's the move locker.
That's the mover right there, again set to it's zero value,
a nil interface.
And then we go ahead on line 54 and we construct
a bike value, there it is, there's our bike right.
And it's an empty struct but there's our bike
and we assign it to the move locker interface.
We can do that because we've implemented that
and if we go back and look at the implementation
of the interface it's using value semantics.
Value semantics.
So we can store copies of values and the addresses.
And we're using value semantics here.
So this is gonna say we have our bike
and we're gonna point to that bike value right there,
there it is.
Remember it's the empty struct
so I could argue that really what we have
is a copy of the bike.
We're using value semantics right?
But since there's no construction anywhere else there,
there's no variable, let's just look at it as
there's our bike.
Now what we're able to do on line 58
is go ahead and say let's assign ML to M.
Two interface values, but we're not really assigning
the interface values.
Remember the only thing that's real is the bike.
So what we're really saying is let's store
the concrete type bike inside of M.
And the compiler knows whether or not this concrete type
has all of the behaviors to satisfy M
because the interface declarations making
and we know that any concrete type inside of move locker
must be able to move, lock, and unlock.
We know all three behaviors exist,
move, lock, and unlock.
Therefore we can do the assignment because the only behavior
we need is move, and so now we say we've got a bike
in here and boom there we go.
We can do that assignment.
However we can't go in the other direction.
I can't assign a mover to a move locker.
Because when it comes to the mover the only behavior
that we are aware of is move.
This is the only thing that we know about.
We don't know if there's a lock and an unlock in here.
When we look at the concrete data
from the mover perspective.
When we look at it from the move locker
perspective we know.
So we're not able to do that assignment.
Now Go has something else that's very special.
It's called type assertions.
And a type assertion in Go allows you to ask a question
about what is stored about the concrete data
that is stored inside the interface.
Allows you to ask a question.
The question we're asking here,
and look at the syntax, the interface value dot
whatever type we want to ask the question around.
What we're asking, and this happens
not at compile time, everything I've told you so far
can be checked at compile time.
Type assertions happen at run time.
What we're seeing on line 76 is
I believe when this code runs
in that moment in time and space
that the concrete value that's sitting inside
of M is a bike value.
That's what we're asking.
Now if it's true that there's a bike value in there,
since we're using value semantics,
B will be a copy of that bike that was stored
it will be a copy.
If it's wrong then we panic.
We have an integrity issue.
You're saying absolutely there needs to be a bike in here
and there isn't alright.
But there is a second form to the type assertion
which is this OK value.
And this is a Boolean flag.
And we want to use this when we may not necessarily
have a concrete value of that type.
Now what this is saying is I believe
there's a bike value inside of M.
If there is great, okay it's true,
B is a copy of the bike.
But if OK is false, then there isn't.
Do not panic.
We know that there's a chance that that might not be true.
And then B isn't a copy of the bike,
it's set to it's zero value or zero value bike.
Type assertions are very very powerful.
And I want to show you some examples of type assertions
and how they are working pretty well
in the standard library.
One of the best places to look is I'm gonna do a search
on Golang IO.
I'm gonna look at the IO package in Go
and I'm gonna come in here and I'm gonna look for
the copy function.
Now anytime you're looking at Go documentation
if you find something you want and you want to look
at the source code, all you gotta do is click on that
header link.
If you look again I found it,
I click on the header link, brings me right to the code.
I really want to look at the unexported implementation
of this copy buffer.
Notice that copy buffer is saying give me any concrete
piece of data that implements writer,
any concrete data that implements reader.
Look at the very first if statement.
And this is interesting.
What it's saying here is the following.
If the concrete data stored inside of the reader variable,
right, source, also implements this other behavior,
notice we're type asserting now not to the concrete
but to another behavior, another interface type.
If the concrete data also implements this other behavior
then let's bypass the default behavior of copy
and use this custom one.
Again what we're able to do is override
this default copy behavior by allowing the user
to have their own implementation of things.
Type assertions are very very powerful as part
of an API design because that means you can design an API
that has a default implementation
but then use interfaces to help override the default.
In fact I'm gonna show you another example of that
exactly looking at this code right here.
Now here I've got a concrete type named user.
When user implements a method called string
using pointer semantics, actually a little more
is going on here.
This method named string that returns a string
implements the stringer interface in the fmt package.
This is a way of overriding the default output behavior
of the fmt package.
But we're using pointer semantics which means what?
Only pointers satisfy the interface.
If I share pointers with fmt we get to override the default.
I share values with fmt, we don't,
we'll get the default behavior.
And let's see that happen live here.
So there I am creating a value of type user
and then I use my value semantics on line 29
and I use my pointer semantics on line 30.
Well using value semantics when we implement
the interface using our pointer receiver means that
we should see the default output which is what we do.
That's the default formatting for a struct value.
But because we now passed the address of U
we're able to override the default thanks to this method
being implemented.
Basically the call to print
is doing that same type assertion.
It's saying hey this concrete data that we passed in,
does it also implement that string method?
If it doesn't, like on line 29, no big deal.
We have our default implementation.
But if it does like on line 30
we get to override the default.
So this is a very powerful API design
through type assertions because it allows
you to have a standard implementation for something
but not bind the user to that, tell the user hey
if you got a better way to do it because maybe
you're on this OS or on this architecture,
or you just know better than I do,
then I'm gonna give you the opportunity to override it.
Type assertions, remember they happen at run time
and they're very powerful parts of designing an API.