nil interfaces: a proposal

40 views
Skip to first unread message

roger peppe

unread,
Dec 8, 2009, 4:11:15 AM12/8/09
to golang-nuts
the fact that typed nil values turn into non-nil interface values
has been brought up here (several times?) before.

having been bitten by this behaviour a couple of times recently,
i thought perhaps it would be nice if there was some way
to make things more intuitive.

here's a way to do it:

if v is a value of interface type, then
v == nil returns nil if either v is a genuinely
null interface or if v's underlying value, w, is nillable
and w == nil.

it should be straightforward to accomplish this,
by adding one bit of information to the Type
structure - is the type a pointer?

then if(v == nil){} would schematically generate this code:
if(v.type == nil || v.type->isptr && (v.data == nil || v.type->size >
sizeof(uint32) && *(void**)v.data == nil){}

this assumes that the built in non-pointer nillable types (slice, string) have
their pointer as their first element, which is currently the case.

it would still be possible to distinguish a genuinely nil
interface value from a typed nil value with:

switch v.(type) {
case nil:
// v is genuinely nil
...
}

it would also be possible to allow:

if _, ok := v.(nil); ok {
}

if desired.

i think that the above rule would prove intuitive and useful
and avoid boilerplate without losing any current capability.

the only down sides that i can see are that it could break
some current code (the compiler could be instrumented to
generate a warning to make it easy to find places to check)
and that nil interface comparison becomes a more
expensive operation.

thoughts?

Ian Lance Taylor

unread,
Dec 8, 2009, 10:29:13 AM12/8/09
to roger peppe, golang-nuts
roger peppe <rogp...@gmail.com> writes:

> if v is a value of interface type, then
> v == nil returns nil if either v is a genuinely
> null interface or if v's underlying value, w, is nillable
> and w == nil.
>
> it should be straightforward to accomplish this,
> by adding one bit of information to the Type
> structure - is the type a pointer?

Unfortunately it's a little more complicated than that. A slice can
be nil, but a slice is not a pointer.

As you know, there are two cases here, and the question is which one
should be easier. For most types, == nil is true exactly for the
uninitialized value. You are suggesting that we change that for
interface types-- == nil will still be true for the uninitialized
value, but it will also be true for some initialized values. It could
be done, but I'm not sure that the argument in favor is compelling
yet.

Ian

roger peppe

unread,
Dec 8, 2009, 11:14:30 AM12/8/09
to Ian Lance Taylor, golang-nuts
2009/12/8 Ian Lance Taylor <ia...@google.com>:
> > it should be straightforward to accomplish this,
> > by adding one bit of information to the Type
> > structure - is the type a pointer?
>
> Unfortunately it's a little more complicated than that. A slice can
> be nil, but a slice is not a pointer.

yes, and the proposal dealt with that. the one bit of information
is actually "is the type nillable?"

> As you know, there are two cases here, and the question is which one
> should be easier.  For most types, == nil is true exactly for the
> uninitialized value.  You are suggesting that we change that for
> interface types-- == nil will still be true for the uninitialized
> value, but it will also be true for some initialized values.  It could
> be done, but I'm not sure that the argument in favor is compelling
> yet.

i agree that it would be a special case, but i think it's justified.
(actually maybe not, see below)

the way i've been thinking of it is that (x == nil)
is just like an method on x, say x.IsNil().

if all nillable types implemented the Nillable interface:

type Nillable interface {
IsNil() bool;
};

then anywhere we wanted to testv against nil,
we could do: v.(Nillable).IsNil()

but making built-in types have methods goes against
the Go grain, so we can't do that.

but it would very useful to be able to generically test against nil
without finding out the exact type. see, for example,
walkIdent, walkCommentGroup, and walkBlockStmt inside
http://golang.org/src/pkg/go/ast/walk.go
all of those functions could be factored out with such
a capability.

actually, thinking about it, maybe just a built-in function isnil
(null?) would do
the job fine. it would work on any interface type
and do the comparison of the contained value against nil
as in my original post.

it would still be easy to trip over the same bug, but at least there
would be an idiomatic way around it.

Ian Lance Taylor

unread,
Dec 8, 2009, 1:00:33 PM12/8/09
to roger peppe, golang-nuts
roger peppe <rogp...@gmail.com> writes:

> 2009/12/8 Ian Lance Taylor <ia...@google.com>:
>> > it should be straightforward to accomplish this,
>> > by adding one bit of information to the Type
>> > structure - is the type a pointer?
>>
>> Unfortunately it's a little more complicated than that. A slice can
>> be nil, but a slice is not a pointer.
>
> yes, and the proposal dealt with that. the one bit of information
> is actually "is the type nillable?"

But I think you need a little more, because the program will need a
way to test whether the type actually is nil. That is, you need more
than a single bit. Or else the program is going to have to fall back
on full type reflection, or call a function, and that might be a
little costly for a simple "== nil".


Your idea of an isnil predeclared function is interesting, and your
point about walkIdent/walkCommentGroup/walkBlockStmt. Others on the
list: do you see a similar issue in your code?

Ian

Russ Cox

unread,
Dec 8, 2009, 1:05:53 PM12/8/09
to roger peppe, Ian Lance Taylor, golang-nuts
why should nil be special?
the following seems like reasonable code

type I interface{ M() }
type T int
func (*T) M() { println("M!") }

var t *T
var i I = t

if i == nil {
println("i is uninitialized")
} else {
i.M()
}

and yet in this proposal the check will
keep me from calling M. if i did call M,
should the call crash to keep the charade
that i == nil alive? also, why shouldn't
this check apply to zero values of other types,
like channels or integers or slices?

i certainly understand the appeal of fixing
the "two nils" problem, but i don't believe
this proposal does that. it just pushes the
problem down a level and leaves the situation
more confusing (and perhaps a little less
error prone) than it is now. but at least
i can explain the current rules, which follow
from go's more general model of what can
be abstracted into an interface.

(in contrast, in a language like oberon (and
java?), the generic representation was a single
pointer, and you get the type by looking just
before the pointer. thus there was no such
thing as a typed nil pointer. that's just a
different model.)

russ

SnakE

unread,
Dec 8, 2009, 1:28:26 PM12/8/09
to r...@golang.org, roger peppe, Ian Lance Taylor, golang-nuts
2009/12/8 Russ Cox <r...@golang.org>

(in contrast, in a language like oberon (and
java?), the generic representation was a single
pointer, and you get the type by looking just
before the pointer.  thus there was no such
thing as a typed nil pointer.  that's just a
different model.)

This somewhat correlates with a question I've got: is there a use case for a non-nil interface{} which contains a typed nil?  Wouldn't it be easier both to explain and to use if any nill value coerced to interface{}(nil) ?

Peter Froehlich

unread,
Dec 8, 2009, 1:34:34 PM12/8/09
to r...@golang.org, roger peppe, Ian Lance Taylor, golang-nuts
On Tue, Dec 8, 2009 at 1:05 PM, Russ Cox <r...@golang.org> wrote:
> (in contrast, in a language like oberon (and
> java?), the generic representation was a single
> pointer, and you get the type by looking just
> before the pointer.

Ah, lumping together Oberon and Java is not very accurate. There are
plenty of differences, for example Oberon has (1) no interface types
of any kind (2) value objects as opposed to just reference/pointer
objects. So... Clarify?
--
Peter H. Froehlich <http://www.cs.jhu.edu/~phf/>
Senior Lecturer | Director, Johns Hopkins Gaming Lab

Russ Cox

unread,
Dec 8, 2009, 1:46:19 PM12/8/09
to SnakE, roger peppe, Ian Lance Taylor, golang-nuts
> This somewhat correlates with a question I've got: is there a use case for a
> non-nil interface{} which contains a typed nil?  Wouldn't it be easier both
> to explain and to use if any nill value coerced to interface{}(nil) ?

again i ask: why should nil be special?
why does the same rule not apply to other zero values?
look at the String method here: http://gopaste.org/view/private:7v2CQ
that seems entirely reasonable (it handles a
nil receiver) but if you introduce this special
case then that code will fail to execute for
fmt.Print((*List)(nil)), because during
reflection it becomes an interface{} and the
nil value would magically disappear.

russ

roger peppe

unread,
Dec 8, 2009, 1:57:26 PM12/8/09
to Ian Lance Taylor, golang-nuts
2009/12/8 Ian Lance Taylor <ia...@google.com>:
>> yes, and the proposal dealt with that. the one bit of information
>> is actually "is the type nillable?"
>
> But I think you need a little more, because the program will need a
> way to test whether the type actually is nil.  That is, you need more
> than a single bit.  Or else the program is going to have to fall back
> on full type reflection, or call a function, and that might be a
> little costly for a simple "== nil".

i think you can do it with a single bit, because for all nillable
types, AFAICS, the pointer is either direct, or in the
first word of the value. which one it is can be determined
by looking at type->size.

Russ Cox

unread,
Dec 8, 2009, 1:58:27 PM12/8/09
to Peter Froehlich, golang-nuts
> Ah, lumping together Oberon and Java is not very accurate. There are
> plenty of differences, for example Oberon has (1) no interface types
> of any kind (2) value objects as opposed to just reference/pointer
> objects. So... Clarify?

Oberon had simple kind of polymorphism based on
creating new types by extending structs with
additional fields. The representation of any of
these was a pointer to the object, and the word
before the object was the type descriptor.
(See pp. 348-350 of the book Project Oberon.)

My understanding of Java was that it used the same
kind of representation so that Object was the same
kind of pointer to memory with type descriptor before it.
Of course there is no one reference for how Java is
implemented so things may have changed or never
been that way. I did put in a question mark.

Anyway, the point is that if you choose that kind of
representation then it avoids having two kinds of nil
but also disallows storing other types in "values of
at least partially unknown type", whatever you choose
to call them.

I think Go's distinction between interface value nil
and typed pointer nil inside an interface value is a
fundamental consequence of allowing any type,
not just pointers, to be stored in an interface value.

Russ

roger peppe

unread,
Dec 8, 2009, 3:42:34 PM12/8/09
to rsc, Ian Lance Taylor, golang-nuts
2009/12/8 Russ Cox <r...@golang.org>:
> why should nil be special?
> the following seems like reasonable code
[...]
> if i == nil {
> println("i is uninitialized")
> } else {
> i.M()
> }

in my original proposal, you'd still be able to do:
switch i.(type) {
case nil:
println("i is uninitialized")
default:
i.M()
}

but i think, following ian's feedback, that isnil() would
scratch my itch quite well. you'd still have
the double nil, but it would be easy to check the
underlying nil in a generic way, which you can't
currently do.

SnakE

unread,
Dec 8, 2009, 6:59:25 PM12/8/09
to r...@golang.org, roger peppe, Ian Lance Taylor, golang-nuts
2009/12/8 Russ Cox <r...@golang.org>

> This somewhat correlates with a question I've got: is there a use case for a
> non-nil interface{} which contains a typed nil?  Wouldn't it be easier both
> to explain and to use if any nill value coerced to interface{}(nil) ?

again i ask: why should nil be special?
why does the same rule not apply to other zero values?

Because nil magically and implicitly converting to non-nil was a great surprise to me.  And it seems like I'm not the only one surprised.

I'm OK with zero values staying zero values exactly because they're values.  Values and references are different enough to warrant distinct, widely accepted terms describing their semantics.

Russ Cox

unread,
Dec 8, 2009, 7:30:36 PM12/8/09
to SnakE, golang-nuts
> Because nil magically and implicitly converting to non-nil was a great
> surprise to me.  And it seems like I'm not the only one surprised.

In the current situation, you get surprised, you learn more
about the underlying data model, and then everything becomes
clear.

If we change the meaning of == nil, then you don't get surprised,
but then you learn more about the underlying data model, and then
everything becomes confusing, because the model doesn't match
the behavior you've already observed. And then you realize there's
this big patch stuck on the side to change one of the consequences
of the model. That feels too much like C++.

If you want different consequences, you should start with
a different model, not tweak the output of the existing one.

Russ
Reply all
Reply to author
Forward
0 new messages