go中基类型方法的指针选择器

Selector of pointer for method from base type in go

很明显,以下代码可以正常工作:

package main

import "fmt"

type T struct {
    a int
}

func (t T) M() {
    fmt.Println("M method")
}

func main() {
    var t = &T{1}
    t.M() // it's interesting
}

但是从specification可以看出:

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.

但在示例中 M 不是来自 *T,而是来自 T。我知道 *T 包含 T 的方法集,但正如我所见,规范并未告诉我们有关 *T 的方法集。我是否理解规范错误或注释行的正确性是基于其他规范规则?

来自Method Sets

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).

你引用了:

x.f denotes the field or method at the shallowest depth in T

引用的是depth"Definition" of depth is:

A selector f may denote a field or method f of a type T, or it may refer to a field or method f of a nested embedded field of T. The number of embedded fields traversed to reach f is called its depth in T. The depth of a field or method f declared in T is zero. The depth of a field or method f declared in an embedded field A in T is the depth of f in A plus one.

关键是嵌入。您的结构没有嵌入单一类型。所以根据定义,你的 T.M 的深度是 zero.

你的t变量是*T类型(即&T{}表达式的类型,即taking the address of a struct composite literal:它生成一个指向唯一变量的指针用文字的值初始化)。

并引用自 Spec: Method values:

As with selectors, a reference to a non-interface method with a value receiver using a pointer will automatically dereference that pointer: pt.Mv is equivalent to (*pt).Mv.

t.M是对非接口方法T.M的引用,由于t是指针,所以会自动解引用:(*t).M().

现在让我们看一个例子,其中 "at the shallowest depth" 确实很重要。

type T struct{}

func (t T) M() { fmt.Println("T.M()") }

type T2 struct {
    T // Embed T
}

func (t T2) M() { fmt.Println("T2.M()") }

func main() {
    var t T = T{}
    var t2 T2 = T2{T: t}
    t2.M()
}

main()中我们调用t2.M()。由于 T2 嵌入了 T,因此可以引用 T2.T.MT2.M。但是由于 T2.T.M 的深度是 oneT2.M 的深度是 zero,所以 T2.MT2 中的最浅深度,因此 t2.M 将表示 T2.M 而不是 T2.T.M,因此上面的示例打印(在 Go Playground 上尝试):

T2.M()