💾 Archived View for ser1.net › post › go-revelations.gmi captured on 2022-07-16 at 13:26:55. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
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.