Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing Mixins and an Inconsistency in Compiler Behavior

Tags:

mixins

go

Mixins can be implemented in Go (1.4.1) using embedding and since struct{} occupies no memory (as I understand) it fits for the situations that we want to add some functionality or just add a method to a type that may actually has nothing to do with it's state, but we like to avoid ParseThing(...) and instead write thing.Parse(...).

So having:

type X struct{}

func (x X) F() {
    fmt.Println("functionality in X.F()")
}

type Y struct{ X }
type Z struct{ Y }

Then if we do:

var z Z
z.F()

Will give us:

functionality in X.F()

So far so good.

Now let's add another type OX with method F() and embed it in Z:

type Z struct {
    Y
    OX
}

type OX struct{} // overriding X

func (x OX) F() {
    fmt.Println("functionality in OX.F()")
}

Interesting! Now we get functionality in OX.F() which shows us that Go compiler searches for the method, starting from type it self and then the last embedded type. We can check that by adding F() to Z:

func (x Z) F() {
    fmt.Println("functionality in Z.F()")
}

The output is functionality in Z.F(). Now if we remove the Z.F() method and add F() to Y:

//func (x Z) F() {
//    fmt.Println("functionality in Z.F()")
//}

func (x Y) F() {
    fmt.Println("functionality in Y.F()")
}

Then we see this error ambiguous selector z.F; redirecting via pointers makes no difference.

Question 1: Why that's so?

The extra level of indirection Y meant for something else, but brought me to this. And as I've guessed func (t T) String() string{} is an exception. This code:

type X struct{}

func (x X) String() string {
    return "in X.String()"
}

type Y struct{ X }
type Z struct {
    Y
    OX
}

type OX struct{} // overriding X

func (x OX) String() string {
    return "in OX.String()"
}

func (x Y) String() string {
    return "in Y.String()"
}

And then this:

var z Z
fmt.Println(z)

Gives us:

{in Y.String() in OX.String()}

Which is logical. But if we use pointer receivers:

import (
    "fmt"
    "testing"
)

func TestIt(t *testing.T) {
    var z Z
    fmt.Println(z)
}

type X struct{}

func (x *X) String() string {
    return "in X.String()"
}

type Y struct{ X }
type Z struct {
    Y
    OX
}

type OX struct{} // overriding X

func (x *OX) String() string {
    return "in OX.String()"
}

func (x *Y) String() string {
    return "in Y.String()"
}

Will print out:

{{{}} {}}

Question 2: Why is that so?

like image 844
Kaveh Shahbazian Avatar asked Dec 25 '22 23:12

Kaveh Shahbazian


1 Answers

Question 1

The compiler is correct. How should it decide, which of OX.F and Y.F should it use? It can't. So it's up to you to call the desired method directly: either with

z.Y.F()

or

z.OX.F()

Edit: As for why your example worked until you've defined F on Y, this is mentioned in the Spec:

For a value x of type T or *T where T is not a pointer or interface type, x.f denotes the field or method at the shallowest depth in T where there is such an f. If there is not exactly one f with shallowest depth, the selector expression is illegal.

(Emphasis added.)

Before you defined the method, the shallowest implementation was OX.F. After you've defined Y.F, there became two Fs on the same level, which is illegal.

Question 2

Again, the compiler is correct. You have embedded types Y and OX into Z, not *Y and *OX. As written in the Spec,

The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).

*T has all methods of T, but not the other way around. Methods sets of OX and Y are empty, so obviously, fmt.Println just prints them as if they were any other kind of struct with no String() method defined.

like image 74
Ainar-G Avatar answered Mar 03 '23 09:03

Ainar-G