Why interface type doesn't provide an “IsNil”

2020-02-16 02:36发布

First let's consider the following:

func process(body io.Reader) {
    fmt.Printf("body == nil ? %+v\n", body == nil)
}

func main() {
    var body *bytes.Buffer
    fmt.Printf("body == nil ? %+v\n", body == nil)
    process(body)
    process(nil)
}

And here's the output:

body == nil ? true
body == nil ? false  // Did you get this right?
body == nil ? true

Another example:

type Container struct {
    Reader io.Reader
}

func processContainer(container Container) {
    fmt.Printf("container.Reader == nil ? %+v\n", container.Reader == nil)
}

func main() {
    var body *bytes.Buffer
    processContainer(Container{Reader: body})
    processContainer(Container{Reader: nil})
}

Output:

container.Reader == nil ? false // Did you get this right?
container.Reader == nil ? true

The explanation for this is at https://golang.org/doc/faq#nil_error.

A naive solution is to make the == nil test just return true if the interface object contains nil value. But this would violate transitivity of ==, as it would assert two objects with nil value but different interface types true under ==.

However, I wonder if there should be an IsNil() method on all interface types, which would solve this issue?

Another example, this line from Go http client, can catch you unexpectedly:

https://github.com/golang/go/blob/master/src/net/http/client.go#L545

So if you call it like this

var body *bytes.Buffer
http.NewRequest(method, path, body)

You'll get a nil pointer exception, even though, by the look of the source code, this shouldn't happen.

Edit

Sorry I referenced the wrong line of the Go http source, now corrected. But the example still holds.

Edit 2

I've highlighted my question to make it clear what I'm asking.

1条回答
三岁会撩人
2楼-- · 2020-02-16 03:10

First read this related question: Hiding nil values, understanding why golang fails here

You can check if the interface value itself is nil by comparing it to nil.

If you want to check if the value wrapped inside a non-nil interface is nil, you can use reflection: reflect.Value.IsNil().

See this modified example:

func process(body io.Reader) {
    fmt.Printf("process(): body == nil ? %t\n", body == nil)
    if body != nil {
        fmt.Println("\tINSIDE IsNil():", reflect.ValueOf(body).IsNil())
    }
}

func main() {
    var body *bytes.Buffer
    fmt.Printf("main(): body == nil ? %t\n", body == nil)
    process(body)
    process(nil)
    process(&bytes.Buffer{})
}

Output (try it on the Go Playground):

main(): body == nil ? true
process(): body == nil ? false
    INSIDE IsNil(): true
process(): body == nil ? true
process(): body == nil ? false
    INSIDE IsNil(): false

If you want a "unified" IsNil() function which tells if either is nil:

func IsNil(i interface{}) bool {
    return i == nil || reflect.ValueOf(i).IsNil()
}
查看更多
登录 后发表回答