I've been trying to build a set of structs that have a base struct as their foundation and variants built on top of that. I've found, however, that there doesn't seem to be a way for the struct to identify itself when the common code is in the base struct. How should I be doing this?
package main
import (
"fmt"
)
type Base interface {
IsMe(other Base) bool
}
type Block struct {
}
func (b *Block) IsMe(other Base) bool {
return b == other
}
type Block2 struct {
Block
}
func main() {
b1 := &Block{}
b2 := &Block2{}
fmt.Printf("b1.IsMe(b1): %v
", b1.IsMe(b1))
fmt.Printf("b1.IsMe(b2): %v
", b1.IsMe(b2))
fmt.Printf("b2.IsMe(b1): %v
", b2.IsMe(b1)) // Wrong result!
fmt.Printf("b2.IsMe(b2): %v
", b2.IsMe(b2)) // Wrong result!
}
package main
import (
"fmt"
"reflect"
)
type Base interface {
IsMe(other Base) bool
}
type Block struct {
_ [1]byte // size of struct must be greater than zero
}
func (b *Block) IsMe(other Base) bool {
x := reflect.ValueOf(b)
y := reflect.ValueOf(other)
return x.Pointer() == y.Pointer()
}
type Block2 struct {
Block // "parent" needs to be first element
}
func main() {
b1 := &Block{}
b2 := &Block2{}
fmt.Printf("b1.IsMe(b1): %v
", b1.IsMe(b1))
fmt.Printf("b1.IsMe(b2): %v
", b1.IsMe(b2))
fmt.Printf("b2.IsMe(b1): %v
", b2.IsMe(b1))
fmt.Printf("b2.IsMe(b2): %v
", b2.IsMe(b2))
}
If you really want to do it the fake inheritance way then you can certainly do it the way you did it but it really only works with unsafe
or reflect
because the language is not designed for what you want to do.
Your problem starts with where x.IsMe
comes from when using embedding. When you write
type Block struct {}
func (b *Block) IsMe(other Base) bool { return b == other }
type Block2 struct { Block }
the method IsMe
is actually associated and bound to Block
instead of Block2
. So calling IsMe
on an instance of Block2
is really only calling it on Block
, in detail:
b2 := Block2{}
fmt.Println(b2.IsMe) // 0x21560
fmt.Println(b2.Block.IsMe) // 0x21560
Both methods have the same address. This shows that even though b2
has the method IsMe
, that method is only propagated from Block
to the outside of Block2
and not inherited. This in turn means that you are always running effectively this code:
b1 := Block{}
b2 := Block2{}
b2_embedded_block := b2.Block
b2_embedded_block.IsMe(b2)
b2_embedded_block.IsMe(b1)
// ...
which obviously cannot work since you are comparing two completely different instances.
What you really should do is to use some function outside of your embedding chain to decide equality. Example (On Play):
func IsEq(a,b Base) bool {
return a == b
}
This actually compares the right instances.