Learning Go: Nil Interface and Nil Implementation

在Russ Cox的这篇帖子里,他说到Go的interface类型是“the most exciting part of Go from a language design point of view”。这么说一点儿也不过分,因为在Go中,任何一种数据类型(包括函数)都可以被附上methods,(在C++和Java等OO语言中,只有class type才能有methods)。而interface就是一个method的signature的集合,如果一个类型的methods包括了一个interfac里列出的methods,那么我们说这个类型“实现了”这个interface。这个类型就算这个interface的一个implementation,可以被当认识这个interface的任何代码使用。所以,在Go中,任何类型都可能被当作某个interface来用。

这种普适性(generality)使得interface类型的用法有些奇妙的地方。比如,在Go Language Specification里说,interface是一种引用类型(reference type)。既然是一种引用类型,一个interface value的值就有可能是nil--表示不引用任何东西。我们暂且将其称为nil interface。

另外,照第一段的讨论,一个指针类型(比如*AType)也可以被附着上一些methods,从而满足某个interface。而这个指针类型的一个nil value(比如var p *AType = nil中的p)被当作这个interface的一个value来用的时候,这个interface中的methods实际上操作不到任何value。我们可以将这种情况称为“一个interface value引用了一个不存在的implementation”。

上述的nil interface value和interface value with nil implementation在Go的内存数据结构里是如何表示的呢?

Russ Cox的帖子里提到,一个interface value包括一个指向data(比如上面提到的p)的指针和一个指向method table(具体的说,叫做eface)的指针。根据这个,我写了这个程序来验证nil interface value和interface value with nil implementation的data structure:

package main
import "fmt"

type Stringer interface {
	String() string
}

type Message uint64
func (m *Message) String() string {
	if m == nil {
		return "[pointer to implementation is nil]";
	}
	return fmt.Sprint(*m)
}

func a(e Stringer) {
	if e == nil {
		fmt.Println("[interface is nil]")
	} else {
		fmt.Println("implementation is ", e.String())
	}
}

func main() {
	a(nil)
	a(Stringer(nil))
	var m Message
	a(&m)
	var p *Message = nil
	a(p)
}

这个程序(叫做learn-interface.go)的编译和执行结果如下:

6g learn-interface.go && 6l -o learn-interface learn-interface.6 && ./learn-interface
[interface is nil]
[interface is nil]
implementation is  0
implementation is  [pointer to implementation is nil]

仔细分析一下这个结果,可以发现:一个 nil interface value就是interface value中的data指针为nil,而一个interface value with nil implementation则是说data指向的指针为nil。