Go revelations
Hoo boy. There are some things which need to percolate in my subconscious for a while before I figure them out; when my brain accomplishes this feat, it’s a thoroughly enjoyable revelation. When I was 20, it would happen in hours, or overnight; now it seems to take a bit longer (a couple of days), but I still enjoy it just as much.
Go has a sort of multiple inheritance based on a rule called shallowest-first. Basically, it means that the compiler determines which function to use based on how many type indirections it has to go through to get to a function. For example:
type F struct { }
func (f F) foo() {}
Here, f.foo()
has a depth of 1 (you only have to dig down one type to get to it). If
type G
inherits from F
:
type G struct {
F
}
then g.foo()
has a depth of 2 – you there’s no foo()
directly on G
, but there is one on the anonymous field
F
. Remember, g.foo()
is really just an alias for
g.F.foo()
– count the points, and you have the depth. And this is where the magic
happens. So consider:
type Dog struct {}
func (d Dog) bark() {}
func (d Dog) wag() {}
type Fox struct {}
func (f Fox) bark() {}
func (f Fox) burrow() {}
Now say you want (for some reason) to have a Gopher
object
that barks and wags like a Dog
, but also
burrows like a Fox
?
type Gopher struct {
Dog
Fox
}
This is illegal: the rules of Go say you can’t have two functions at
the same depth, and the bark()
s here conflict. In this case, you have two depth-2
barks()
. But here’s what you can do:
type deeperFox struct {
Fox
}
type Gopher struct {
Dog
deeperFox
}
Now, the Fox
functions are one deeper than the Dog
functions; it’s now legal, the compiler is happy, and it is clear which
bark()
will be called. If you really mean Fox.bark()
,
you can still get at it with g.deeperFox.bark()
.
When I first came across this in code, I couldn’t figure out why someone would do this. The Go specification section on selectors wasn’t a whole lot of help in this case, at least for me. I find this sort of thing a little bit hacky, but it works, it doesn’t require a lot of code, and it is straight-forward. Much like the rest of Go.