2015-11-15
I should remember that I can cast things in Go
I think of Go as a strongly typed language.
My broad and somewhat reflexive view of strongly typed languages is
that they mostly don't allow you to cast things around because most
casts will require expensive data conversion and the language wants
you to do that explicitly, with your own code. Go even sticks to this
view; you can cast numbers around (because it's too useful) and you
can go between string
and []byte
(because it's a core operation),
and that's mostly it.
(Then there's interfaces, which expose some tricks. Interface casting involves a bunch of potentially expensive magic, but it's a core feature of Go so it's an exception to the 'no expensive operations via casts' rule of thumb.)
However, there is an important practical exception to this, which
comes about because of another thing that Go encourages: lots of
named types that you derive from fundamental types. Rather than using,
say, int
, for all sorts of different things in your code, everyone
knows that you should instead create various specific types:
type Phase int type Action int type OneMap map[string]string type TwoMap map[string]string
This way you can never accidentally use a Phase
when the actual
function, field, or whatever is supposed to be an Action
, or pass
a OneMap
function a TwoMap
, and so on. Go's strong typing will
force them to be separate (even if this is sometimes irritating,
for example if you're dealing with cgo).
These derived types can be cast to each other and to their underlying type. This is not just if they're numbers; any derived type can be cast around like this, provided that the underlying 'real' types are the same (per the Conversions section of the language spec).
(At a mechanical level it's easy to see why this is okay; since the two derived types have exactly the same memory layout, you don't have to do expensive conversion to generate a type-safe result.)
Now, ordinarily you still don't want to cast a OneMap
to a TwoMap
(or to a map[string]string
). But there is one special case that
matters to me, and that's if I want to do the same operation on
both sorts of maps. Since I actually can cast them around, I don't
need to write two duplicated blocks of (type-specific) code to do
the same operation. Instead I can write one, perhaps one that's
generic to the map[string]string
type, and simply call it for
both cases through casts. This is not the only way to create common
code for a generic operation but it's probably the easiest one to
add on the fly without a bunch of code refactoring.
So this is why I need to remember that casting types, even complex types, is something that I can do in Go. It's been kind of a reflexive blind spot in my Go code in the past, but hopefully writing this will avoid it in the future.