4 ms·
The nil-interface wart actually does result in comparison issues, and it's something that trips people up all the time: package main import ( "f
by lobster_johnson 9y ago
The nil-interface wart actually does result in comparison issues, and it's something that trips people up all the time:
package main
import (
"fmt"
"io"
)
type Thing struct{}
func (t *Thing) Close() error { return nil }
func main() {
var x *Thing
var y io.Closer = x
fmt.Printf("x == nil -> %#v\n", x == nil) // true
fmt.Printf("y == nil -> %#v\n", y == nil) // false
}
Here, both x and y are conceptually nil, but y cannot be compared to nil.
Go is a strict language, but it's curiously lax about many things, including this. "go vet" won't even warn you that you're unintentionally wrapping nil in an interface. This goes way beyond just a trivial example like the above one, though: Any general-purpose code that has to deal with arbitrary interface{} values can't just check for nils, but need to do things like this:
func UnwrapReflectValue(v reflect.Value) interface{} {
if !v.IsValid() {
return nil
}
if v.IsNil() {
return nil
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
if v.IsNil() {
return nil
}
}
if v.Kind() == reflect.Interface {
v = v.Elem()
}
return v.Interface()
}
I don't know why Go doesn't let "y == nil" return true here; boxed values are supposed to have the same high-level semantics as unboxed ones, and off the top of my head I can't think of any code where changing the current behaviour would cause breakage.
- weberc2 9y agoYour mental model for an interface is incorrect--an interface is just a pointer. A pointer can be nil, but some pointers point to other nillable types. If we paper over this for nil checks, we destroy states that are semantically valid/important. Abstracting as you propose creates more surprising behavior than it fixes. On the other hand, all it takes to avoid the problem altogether is the understanding that interfaces are just (fat) pointers.
- lobster_johnson 9y agoMy mental model is just fine, thanks; I've written about 70k lines of Go, and I'm perfectly aware of how it works. I'm saying it's a misfeature. interface{} is a leaky abstraction.
- weberc2 9y agoIt's definitely a leaky abstraction, but not for the reasons you mentioned. Your proposal just adds more leakiness on top of it.
- Merovius 9y ago> Here, both x and y are conceptually nil, but y cannot be compared to nil. I disagree with the "conceptual nilness" of y. x is a perfectly fine and usable implementation of io.Closer. It doesn't even panic. Having a nil-check (i.e. "does this interfaces contain a value?") return true seems dishonest to me. I agree, that it was a bad choice to call both the zero-value of pointers and the zero-value of interfaces "nil". But in general, the possibility of nil-pointers being valid values and implementers of interfaces is useful. For example if you have a linked list or graph-structure. I also asked about it on reddit and got this answer: https://www.reddit.com/r/golang/comments/6sktsr/typed_nils_in_go_2/dldjtfi/ https://www.reddit.com/r/golang/comments/6sktsr/typed_nils_i...