Relearning Go: Day 5
Interfaces (continued)
I’m about ⅓ of the way through the examples.
Interfaces (continued)
“Pointers and receivers”
Last time I was reading through How to use interfaces in Go. I was about to pick up at “Pointers and interfaces” when I remembered something earlier in the post:
I think Russ Cox’s description of interfaces is very, very helpful.
Let’s go and read that and see if it helps my understanding.
“Go Data Structures: Interfaces”
Short answer: yes. Longer answer: yes, but I don’t feel any better about it.
What I learned:
[]stringcannot be used as[]interface{}, for example, because their “interface values” are different.- An interface value is a pointer to an “interface table” and a pointer to the data (modulo optimisations).
- An interface table is a pointer to the concrete type plus pointers to interface methods for one interface. This is calculated and cached at run-time and is only for the specific combination of (concrete type, interface type).
- Dispatching an interface method call costs a dereference to the interface table, a lookup of the interface method, then a jump to the method.
- Were Go to allow it (it doesn’t), dispatching for a different interface type would necessitate a dereference to the interface table, a dereference to the concrete type, a lookup of the interface table for (concrete type, interface type) or a calculation & caching thereof if not already available, then a lookup of the interface method.
- We’re told this is duck typing but it’s only skin deep. Indeed, not even that: the interface values for identical interfaces are different.
Summed up:
- Implementing interfaces is low friction; they have convenient ergonomics.
- Interface values are optimisations that break those ergonomics.
- One must understand how these optimisations work to use interfaces effectively, further undermining the notion of Go as a language suitable for beginners. I found this confusing and I’ve been programming for decades (of course, maybe I’m stupid).
“Pointers and receivers” (again)
Popping the stack back to How to use interfaces in Go § Pointers and interfaces, and… I read and reread this section several times, and I experimented in code, but I don’t fully understand what it’s saying.
Let me write out what I understand. First, the example code (reduced to
something minimal) with the amended (c *Cat) Speak() string method:
type Animal interface {
Speak() string
}
type Dog struct {}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct {}
func (c *Cat) Speak() string {
return "Meow!"
}
func main() {
animals := []Animal{Dog{}, Cat{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}
Compiling it I’m getting:
cannot use
Cat{}(value of typeCat) asAnimalvalue in array or slice literal:Catdoes not implementAnimal(methodSpeakhas pointer receiver)
The article says changing Cat{} to new(Cat) or &Cat{} will make it valid.
What it’s saying is not that the interface
Animaldemands that you define your method as a pointer receiver, but that you have tried to convert aCatstruct into anAnimalinterface value, but only*Catsatisfies that interface.
Okay, that makes some sense.
Next it says that I can switch out Dog{} for new(Dog) or &Dog{}, and
indeed that does work. But why?
This works because a pointer type can access the methods of its associated value type, but not vice versa. That is, a
*Dogvalue can utilize theSpeakmethod defined onDog, but as we saw earlier, aCatvalue cannot access theSpeakmethod defined on*Cat.
Okay, but I have seen that I can call pointer-receiving methods on values – in some situations:
cat := Cat{} // or: `var cat Cat = Cat{}`
cat.Speak()
That compiles fine, but the following does not compile:
Cat{}.Speak() // or: `(Cat{}).Speak()`
No idea why 🤷
It continues:
Since everything is passed by value, it should be obvious why a
*Catmethod is not usable by aCatvalue; any oneCatvalue may have any number of*Catpointers that point to it. If we try to call a*Catmethod by using aCatvalue, we never had a*Catpointer to begin with.
This makes no sense. Why can’t a *Cat method act on a pointer to the Cat
value that I’m holding? Why does it matter that there may be other pointers to
the same Cat?
Conversely, if we have a method on the
Dogtype, and we have a*Dogpointer, we know exactly whichDogvalue to use when calling this method, because the*Dogpointer points to exactly oneDogvalue; the Go runtime will dereference the pointer to its associatedDogvalue any time it is necessary.
Yeah… of course. I missing something, aren’t I? What’s this “exactly one Dog
value” stuff? We only had one Cat value up above too. Am I going crazy?
I’m backing up to the Go by Example: Interfaces page, where it looks like interfaces are not a big topic, and yet this has been so confusing. It became more confusing after reading the linked blog post, then even more confusing when reading Russ Cox’s blog post about it. None of these was able to explain it in a way that I can understand.
I am reading this with negative bias, that is clear. That could explain all of this; I don’t want to pretend like I’m an objective learner here. But but but, I have learned Haskell and Rust and found them easier to digest, and they have terrifying reputations. Terrifying reputations, but consistent mental models that reward the diligent learner. I have been diligent here, learning Go, and I have not been rewarded.
That’s enough for today.