实现混合和编译器行为的不一致

Implementing Mixins and an Inconsistency in Compiler Behavior

Mixins 可以在 Go (1.4.1) 中使用嵌入实现,并且由于 struct{} 不占用内存(据我了解)它适合我们想要添加一些功能或只是添加一个方法的情况到一个实际上可能与它的状态无关的类型,但我们想避免 ParseThing(...) 而是写 thing.Parse(...).

所以有:

type X struct{}

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

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

那么如果我们这样做:

var z Z
z.F()

会给我们:

functionality in X.F()

到目前为止一切顺利。

现在让我们使用方法 F() 添加另一种类型 OX 并将其嵌入 Z:

type Z struct {
    Y
    OX
}

type OX struct{} // overriding X

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

有意思!现在我们得到 functionality in OX.F() ,它告诉我们 Go 编译器搜索方法,从类型本身开始,然后是最后一个嵌入类型。我们可以通过将 F() 添加到 Z:

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

输出为functionality in Z.F()。现在,如果我们删除 Z.F() 方法并将 F() 添加到 Y:

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

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

然后我们看到这个错误ambiguous selector z.F;通过指针重定向没有区别。

问题一:为什么会这样?

额外的间接级别 Y 意味着别的东西,但把我带到了这里。正如我所猜测的,func (t T) String() string{} 是一个例外。此代码:

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()"
}

然后是这个:

var z Z
fmt.Println(z)

给我们:

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

这是合乎逻辑的。但是如果我们使用指针接收器:

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()"
}

将打印出:

{{{}} {}}

问题2:为什么会这样?

问题 1

编译器是正确的。它应该如何决定,它应该使用 OX.FY.F 中的哪一个?它不能。因此,您可以直接调用所需的方法:使用

z.Y.F()

z.OX.F()

编辑: 至于为什么在 Y 上定义 F 之前您的示例一直有效,这在 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.

(强调已添加。)

在您定义方法之前,最浅层的实现是 OX.F。你定义了Y.F之后,在同一层上变成了两个F,这是不合法的。

问题 2

同样,编译器是正确的。您将类型 YOX 嵌入到 Z 中,而不是 *Y*OX。如 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 具有 T 的所有方法,但 不是相反。 OX 和 [=15= 的方法集] 是空的,所以很明显,fmt.Println 只是打印它们,就好像它们是任何其他类型的结构一样,没有定义 String() 方法。

Ainar-G 写出工整的答案

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.

我想补充一点

Spec:

If S contains an anonymous field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.

所以如果你只是使用引用来提升方法,事情就会起作用

fmt.Println(&z)

但这会导致选择歧义,因为字符串方法的可能性很小,因此选择器字符串由于规范而非法。编译器必须抱怨,但它没有。这种行为看起来不确定,在我看来只能解释为普通打印操作的特例。 这将按预期工作

var y Y
fmt.Println(&y)

这是工作示例 Playground