method set, interface, auto deference or reference 當這些語法綜合再一起使用時,很容易混淆,甚至寫出 bug code,這章我們就透過題目探討一下 method set 常常被大家誤會的地方。

Golang 78 語法篇 - interface % Line 50 用到的概念就在這篇。

題目

請問以下程式輸出結果為何

type Employee struct {}

func (e Employee) DoWork() {
	fmt.Println("do work")
}

func main() {
	(&Employee{}).DoWork()
}

答案只有兩種方向: 1. 無法編譯 2. 印出 do work

這個程式可以編譯,並且還印出 do work 但是為什麼呢?

Method set

在 receiver argument 和 receiver parameter 不一樣的情況下,直覺反應就是 implicit dereferenceimplicit reference 在背後會幫忙,但是這個寫法 (struct literal) 是不會有此情況的。

既然沒有 implicit dereferenceimplicit reference 的支援,那一定存在其它機制使得這段 code 能正常編譯。

以下從 Golang Specification所截取的句子:

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)

以下是從 Golang FAQ 所截取的句子:

As the Go specification says, the method set of a type T consists of all methods with receiver type T, while that of the corresponding pointer type *T consists of all methods with receiver *T or T. That means the method set of *T includes that of T, but not the reverse

其實意思就是型態 T 的所有 method 包含了所有 receiver 型態是 T 的 method,這理所當然,看起來也像廢話,另一句才是關鍵,也是今日的重點:*T 型態的所有 method 包含了 receiver 型態是 T 及 *T 的 method。

有了此結論我們就能知道 *Employee 的 method set 裡是有 DoWork(),所以編譯會過。

那這就有趣了,再看一個延伸題,請問這段 code 編譯會不會過:

type Worker interface {
	DoWork()
}

type Employee struct {
}

func (e Employee) DoWork() {
	fmt.Println("do work")
}

func main() {
	var w Worker = &Employee{}
}

這段 code 問的其實就是 Employee 到底有沒有符合(satisfy) Worker 介面,根據剛剛所得出的結論, *Employee 的 method set 裡是有包含 DoWork 的,所以編譯是會過的。

Reference

  1. Golang Frequently Asked Questions (FAQ)
  2. The Go Programming Language Specification - Method sets