在Go语言中,特别是在错误检查方面,经常会看到nil检查,这是由于Go语言的特殊错误处理约定。在大多数情况下,nil检查是直截了当的,但在接口情况下,情况会有所不同,需要特别小心。
看看下面的代码片段,猜猜输出结果会是什么。
package main
import (
"bytes"
"fmt"
"io"
)
func check(w io.Writer) {
if w != nil {
fmt.Println("w is not nil")
}
fmt.Printf("w is %+v\n", w)
}
func main() {
var b *bytes.Buffer
check(b)
fmt.Printf("b is %+v", b)
}
以下是输出
w is not nil
w is
b is
在check()方法中,你可能期望在该情况下w是nil。但实际上并非如此。当打印该对象时,它才变成nil。这是如何发生的呢?
原因在于接口具有特殊的实现方式,它包含两个组成部分:类型(Type)和值(Value)。在底层,接口以类型T和值V的形式实现。V是一个具体的值,比如int、struct或指针,但绝不会是接口本身,并且具有类型T。例如,如果我们将值3存储在一个接口中,那么得到的接口值在概念上表示为(T=int,V=3)。值V也被称为接口的动态值,因为在程序执行过程中,给定的接口变量可能会持有不同的V值(和相应的T类型)。
只有当V和T都未设置时,接口值才为nil(T=nil,V未设置)。特别地,一个nil接口总是持有一个nil类型。如果我们将类型为*int的nil指针存储在接口值中,内部类型将始终为*int,而不管指针的值如何(T=*int,V=nil)。因此,即使指针值V为nil,这样的接口值仍然是非nil的。
因此,在上述情况下,创建变量b时,它的类型是*bytes.Buffer,但其值是nil。因此,你会看到上述的输出。让我们看一个更具体的例子,说明何时一个接口值会为nil。
package main
import (
"fmt"
)
type SomeError struct{}
func (se *SomeError) Error() string {
return "error"
}
func check(e error) {
if e == nil {
fmt.Println("e is nil")
}
fmt.Printf("e is %+v\n", e)
}
func main() {
var e error = nil
check(e)
var se *SomeError = nil
check(se)
}
当创建变量e时,它是一个错误(error),但没有具体的错误类型。它的值是nil,因此将其与nil进行比较将返回true。
e is nil
e is <nil>
e is error
在使用接口作为函数参数并进行nil检查时要非常小心,它可能不会返回你所期望的结果。
参考:Be careful about nil check on interface in GoLang | Pixelstech.net