This is the nearest approximation to sane polymorphism with both plain and pure virtual functions that I have found thus far. By the very nature of Go's design and the goal at hand, it is ugly but effective.
package main
import (
"fmt"
)
type I interface {
foo(s string) // Our "pure virtual" function
bar()
}
type A struct {i I}
type B struct {A}
type C struct {B}
// fk receivers, this is a "member function" so I'll use OO nomenclature
func (this *A) init(i I) {
this.i = i // the i contains (internal) pointers to both an object and a type
}
func (this *A) bar() {
this.i.foo("world")
}
func (this *B) foo(s string) {
fmt.Printf("hello %s\n", s)
}
func (this *C) foo(s string) {
fmt.Printf("goodbye cruel %s\n", s)
}
func main() {
var i I
b := &B{}
b.init(b) // passing b as the parameter implicitly casts it to an I interface object
b.bar()
c := &C{}
c.init(c)
c.bar() // c is a pointer to C, so Golang calls the correct receiver
i = b
i.bar()
i = c
i.bar() // Internally, i contains pointers to the C object and the C type,
// so that the correct receiver is called
}
https://play.golang.org/p/4qBfmJgyuHC
In real OO languages, each object of a class with any virtual functions must have a pointer to a virtual function table or a least type that maps to it. So adding an interface member to the base (embedded) struct only wastes an additional machine word for the pointer that will just point to its self.
Alternatively, we could remove the I interface member from A and the have pure virtual member function just accept the implementation as an argument.
type I interface {
foo(s string)
bar(i I)
}
type A struct {}
type B struct {A}
type C struct {B}
func (this *A) bar(i I) {
i.foo("world")
}
https://play.golang.org/p/9gvaCuqmHS8
But at this point, foo is no longer a pure virtual function, API users could pass any value whose type implements I, and the whole OO concept is broken. The object pointer portion of the I interface passed to bar doesn't need to be the same as this, but then again we didn't need to pass that same value using an init() function, but at least only an API user in the same package would be allowed to set it.
At this point, we have transitioned to the Go way of doing things: compositional programming with dependency injection. This is what Ken Thompson and the other designers thought was a better way to go -- at least for their stated goals. While it is vastly inferior in MANY respects, it does create many advantages and I won't argue those points that here.