Why is it valid to assign <nil> to an interface type in Go?

huangapple go评论84阅读模式
英文:

Why is it valid to assign <nil> to an interface type in Go?

问题

package main

import "fmt"

type TesterInterface interface{
    Yell()
}

type Tester struct{}

func (t Tester) Yell() {
    fmt.Println("HELLO")
}

func main() {
    var t TesterInterface
    t = Tester{}
    t.Yell()
    t = nil
    t.Yell()
}

我预期编译器会在第19行(t = nil)报错,因为nil并没有实现Yell()方法,然而程序运行并输出以下结果:

HELLO
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0xffffffff addr=0x0 pc=0x201a9]

goroutine 1 [running]:
runtime.panic(0xef060, 0x1b3d44)
    /tmp/sandbox/go/src/pkg/runtime/panic.c:266 +0xe0
runtime.panicstring(0x1b3d44, 0x1b85)
    /tmp/sandbox/go/src/pkg/runtime/panic.c:489 +0x120
runtime.sigpanic()
    /tmp/sandbox/go/src/pkg/runtime/os_nacl.c:254 +0x80
main.main()
    /tmpfs/gosandbox-229cdcdf_329829af_705be907_f12cf4dc_ed51e32e/prog.go:20 +0xa9
runtime.main()
    /tmp/sandbox/go/src/pkg/runtime/proc.c:220 +0x1c0
runtime.goexit()
    /tmp/sandbox/go/src/pkg/runtime/proc.c:1394

goroutine 2 [syscall]:
runtime.notetsleepg(0xfeefdf88, 0x0, 0xf8475800, 0xd)
    /tmp/sandbox/go/src/pkg/runtime/lock_sema.c:254 +0xa0
runtime.MHeap_Scavenger()
    /tmp/sandbox/go/src/pkg/runtime/mheap.c:463 +0xc0
runtime.goexit()
    /tmp/sandbox/go/src/pkg/runtime/proc.c:1394
created by runtime.main
    /tmp/sandbox/go/src/pkg/runtime/proc.c:179
 [process exited with non-zero status]

Program exited.

为什么编译器认为将nil赋值给接口是合法的?

此外,根据Go文档:

接口类型的变量可以存储任何具有方法集的类型的值,该方法集是接口的任何超集。

那么,"nil"如何包含一个方法集,该方法集是TesterInterface的超集?文档继续说:

未初始化的接口类型变量的值为nil。

但这似乎与第一条语句相矛盾。

英文:

http://play.golang.org/p/_GP3RZTh4Q

package main

import &quot;fmt&quot;

type TesterInterface interface{
    Yell()
}

type Tester struct{}

func (t Tester) Yell() {
	fmt.Println(&quot;HELLO&quot;)
}

func main() {
	var t TesterInterface
	t = Tester{}
	t.Yell()
	t = nil
	t.Yell()
}

I would expect the compiler to complain about line 19 (t = nil), since nil doesn't implement Yell(), however the program runs and provides the following output:

HELLO
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0xffffffff addr=0x0 pc=0x201a9]

goroutine 1 [running]:
runtime.panic(0xef060, 0x1b3d44)
    /tmp/sandbox/go/src/pkg/runtime/panic.c:266 +0xe0
runtime.panicstring(0x1b3d44, 0x1b85)
    /tmp/sandbox/go/src/pkg/runtime/panic.c:489 +0x120
runtime.sigpanic()
    /tmp/sandbox/go/src/pkg/runtime/os_nacl.c:254 +0x80
main.main()
    /tmpfs/gosandbox-229cdcdf_329829af_705be907_f12cf4dc_ed51e32e/prog.go:20 +0xa9
runtime.main()
    /tmp/sandbox/go/src/pkg/runtime/proc.c:220 +0x1c0
runtime.goexit()
    /tmp/sandbox/go/src/pkg/runtime/proc.c:1394

goroutine 2 [syscall]:
runtime.notetsleepg(0xfeefdf88, 0x0, 0xf8475800, 0xd)
    /tmp/sandbox/go/src/pkg/runtime/lock_sema.c:254 +0xa0
runtime.MHeap_Scavenger()
    /tmp/sandbox/go/src/pkg/runtime/mheap.c:463 +0xc0
runtime.goexit()
    /tmp/sandbox/go/src/pkg/runtime/proc.c:1394
created by runtime.main
    /tmp/sandbox/go/src/pkg/runtime/proc.c:179
 [process exited with non-zero status]

Program exited.

Why does the compiler consider it legal to assign nil to an interface?

Additionally, from the Go-docs:

> A variable of interface type can store a value of any type with a method set that is any superset of the interface.

How does "nil" contain a method set that is a superset of TesterInterface? The docs go on to say that

> The value of an uninitialized variable of interface type is nil.

But this seems to contradict the first statement.

答案1

得分: 8

《Go编程语言规范》

零值

当分配内存来存储一个值时,无论是通过声明还是通过调用make或new,如果没有提供显式的初始化,该内存将被赋予默认初始化。这样的值的每个元素都被设置为其类型的零值:布尔类型为false,整数类型为0,浮点数类型为0.0,字符串类型为"",指针、函数、接口、切片、通道和映射类型为nil。

对于接口类型的变量,值nil是有效的,因为它是接口类型的零值。

《Go数据结构:接口》一文描述了接口值的实现。

英文:

> The Go Programming Language Specification
>
> The zero value
>
> When memory is allocated to store a value, either through a
> declaration or a call of make or new, and no explicit initialization
> is provided, the memory is given a default initialization. Each
> element of such a value is set to the zero value for its type: false
> for booleans, 0 for integers, 0.0 for floats, "" for strings, and nil
> for pointers, functions, interfaces, slices, channels, and maps.

The value nil is valid for an interface type variable because it's the zero value for the interface type.

The Go Data Structures: Interfaces article describes the implementation of interface values.

答案2

得分: 6

当你问类似“为什么编译器认为它是合法的…”这样的问题时,情况总是很棘手的。答案通常是因为这是语言规定的,并且每个接口都接受nil值。但这个问题还有更多方面的考虑:接口的实现以及如果Go语言不是这样工作的话会怎样。

首先考虑后者,如果nil不是接口的合法值,你将如何替代常见的惯用法:

func Something() error {
    ...
    return nil
}

这依赖于nil是一个有效的error。类似地,指针满足接口也是非常常见的。如果nil不满足接口,你将如何返回nil指针呢?记住,nil只是一个值。

至于实现方面,请参考为什么我的nil错误值不等于nil关键点在于接口由类型和值的元组组成,对于每个接口,元组(nil, nil)是有效的。

我假设你同意“类型化的nil”(即某个特定指针类型的nil)应该可以赋值给由其指针类型实现的接口,对吗?因此,接口必须被视为可能为nil,并且你必须以与检查指针相同的方式检查它们是否为nil。因此,排除无类型的nil并没有安全上的好处,但会破坏你可以对接口进行的许多有用操作(比如返回“无错误”)。

(最后一部分有一点微妙之处。仔细阅读一下“为什么我的nil错误值不等于nil?”你会意识到你不能简单地对持有类型化nil值的接口进行nil检查。你只能通过将接口类型断言为其底层指针类型或使用反射来进行检查。这并没有从根本上改变观点,但我不想掩盖这个问题。)


编辑

根据下面@newacct的评论,删除了一些多余的观点。

PeterSO的回答也非常重要。每种类型都必须有一个零值。接口的零值除了nil还能是什么呢?

英文:

It is always dicey when you ask questions like "why does the compiler consider it legal…" The answer, as usual, is because that's the language, and every interface accepts the nil value. But there are more sides of this: the implementation of interfaces, and how Go would work if it were not this way.

Starting with the latter, consider the problem if nil were not a legal value for interfaces. How would you replace the common idiom:

func Something() error {
    ...
    return nil
}

This relies on the fact that nil is a valid error. <strike>Similarly, it is very common that pointers satisfy an interface. If nil did not also satisfy the interface, how would you return the nil pointer? Remember, nil is just a value.</strike>

To the implementation, see Why is my nil error value not equal to nil? The key point is that an interface is made up of a tuple of type and value, and for every interface, the tuple (nil, nil) is valid.

<strike>I assume you agree that a "typed nil" (i.e. a nil of some specific pointer type) should be assignable to an interface that is implemented by its pointer type, correct? So interfaces must be treated as though they could be nil, and you must nil-check them in exactly the same ways that you nil-check pointers. So there is no safety benefit to excluding the untyped nil, but you'd break many useful things you can do with interfaces (like return "no error").

(There's a slight subtlty to this last part. Read carefully again "Why is my nil error value not equal to nil?" and you'll realize that you can't trivially nil-check an interface that holds a typed nil value. You can only do that by type asserting the interface into its underlying pointer type or using reflect on it. This doesn't dramatically change the point, but I didn't want to gloss over that problem.)</strike>


EDIT

Based on @newacct's comments below, struck some extraneous points.

PeterSO's answer is also very important. Every type must have a zero value. What else would the zero value for an interface be but nil?

huangapple
  • 本文由 发表于 2014年5月2日 02:40:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/23414240.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定