I really think this was a mistake in the design. Comparing an untyped nil to an interface should compare to the underlying value. The current behavior can be achieved with reflect.ValueOf(v).IsValid(). But it’s too late to change now.
I think the mistake was using the name “nil” for both the zero value of pointers and the zero value of interfaces. In retrospect, there should have been a name that was unique to interfaces, like “none”.
You’re probably right, but then you lose the way to tell whether you can call methods on an interface value.
type S struct
func (s *S) Foo() { println("fooing") }
var f Fooer = (*S)(nil)
f.Foo() // prints "fooing" even though f "is nil"
var f2 Fooer
f2.Foo() // panics
f == f2 // true under your proposal, false in real Go
IMHO the root mistake is having all types have zero values. I don’t think interface or pointer types should have zero values. My (very amateur) nil-safe dialect did away with them.
Saying “there are many different kinds of nil in Go” is misleading when the author points [sic] to different pointer types. That behavior is no different from C or any other statically typed language with nulls. I don’t see these as different values, really, although I guess you could make a case for it. But it doesn’t explain the weird behavior in the first example.
I agree with @icholy that the way Go compares interface values to nil is a mistake in the language. Any interface whose value pointer is nil should compare equal to nil. I don’t think a different nil-for-interfaces value would help; it would just ad even more confusion.
This is simpler if you think about nil values as being part of an algebraic type system such that T* is actually implicitly T* | Nil. It’s then clear that U* is U* | Nil and any comparison of T* | Nil against U* | Nil is first a pattern match on whether either both things are the same type and then equivalent. If they’re the same type, then they are compared.
Part of the problem with Go is that they have a structural type system (structs are concrete types, interfaces are structural types) but they only have part of an algebraic type system. You can express intersections by embedding an interface in another (i.e. you can write A & B in the Go type language) but you can’t express union types (A | B). This means that you must treat nil as a weird and special thing.
nil is an identifier for the zero value in many of the language’s types. It’s reminiscent of null in other languages, but it would be a mistake to treat them the same way. nil is not null.
null having odd or langage specific behaviours is nothing novel. Quite the opposite really.
I really think this was a mistake in the design. Comparing an untyped nil to an interface should compare to the underlying value. The current behavior can be achieved with
reflect.ValueOf(v).IsValid()
. But it’s too late to change now.I think the mistake was using the name “nil” for both the zero value of pointers and the zero value of interfaces. In retrospect, there should have been a name that was unique to interfaces, like “none”.
You’re probably right, but then you lose the way to tell whether you can call methods on an interface value.
IMHO the root mistake is having all types have zero values. I don’t think interface or pointer types should have zero values. My (very amateur) nil-safe dialect did away with them.
Saying “there are many different kinds of nil in Go” is misleading when the author points [sic] to different pointer types. That behavior is no different from C or any other statically typed language with nulls. I don’t see these as different values, really, although I guess you could make a case for it. But it doesn’t explain the weird behavior in the first example.
I agree with @icholy that the way Go compares interface values to nil is a mistake in the language. Any interface whose value pointer is nil should compare equal to nil. I don’t think a different nil-for-interfaces value would help; it would just ad even more confusion.
This is simpler if you think about nil values as being part of an algebraic type system such that
T*
is actually implicitlyT* | Nil
. It’s then clear thatU*
isU* | Nil
and any comparison ofT* | Nil
againstU* | Nil
is first a pattern match on whether either both things are the same type and then equivalent. If they’re the same type, then they are compared.Part of the problem with Go is that they have a structural type system (structs are concrete types, interfaces are structural types) but they only have part of an algebraic type system. You can express intersections by embedding an interface in another (i.e. you can write
A & B
in the Go type language) but you can’t express union types (A | B
). This means that you must treat nil as a weird and special thing.The generics feature sort of enables union types, but only in limited contexts.
Come on,
undefined
would be so much fun.null having odd or langage specific behaviours is nothing novel. Quite the opposite really.