in case you were confused about nil...

1,875 views
Skip to first unread message

Aaron Bohannon

unread,
May 28, 2012, 12:47:46 AM5/28/12
to golan...@googlegroups.com
In case you were confused about whether nil == nil, this program should help clear things up:


It prints a nice little reference chart.

 - Aaron

Jesse McNelis

unread,
May 28, 2012, 2:10:37 AM5/28/12
to Aaron Bohannon, golan...@googlegroups.com
Wow, you made something rather simple seem really complicated.
nil is an untyped constant like 0
It's the zero value of some types: maps, slices, pointers, interfaces
and channels

When you compare two interface values, the values and the types
contained within are compared.

When you compare an interface and a concrete value, the concrete value
is implicitly converted to an interface value for the comparison.

The confusion people have with nil is that they don't realised that an
interface value is distinct from the value it contains. Which is the
result of an assumption that interfaces in Go are similar to
interfaces in Java etc.



--
=====================
http://jessta.id.au

Aaron Bohannon

unread,
May 28, 2012, 7:59:58 PM5/28/12
to golan...@googlegroups.com, Aaron Bohannon
On Monday, May 28, 2012 2:10:37 AM UTC-4, Jesse McNelis wrote:
Wow, you made something rather simple seem really complicated. 

I actually never thought it was simple to begin with. 

nil is an untyped constant like 0
It's the zero value of some types: maps, slices, pointers, interfaces
and channels 

Perhaps this is a better way to look at things: nil _is_ simple, but having "untyped constants" in a statically typed language is not.

 - Aaron 

Corey Thomasson

unread,
May 28, 2012, 10:06:33 PM5/28/12
to Aaron Bohannon, golang-nuts

I think I accidentally off-list

On May 28, 2012 10:05 PM, "Corey Thomasson" <cthom...@gmail.com> wrote:

What statically typed language does not have untyped constants? C, C++, C#, and Java all certainly do.

Thomas Bushnell, BSG

unread,
May 29, 2012, 6:48:52 AM5/29/12
to Corey Thomasson, golang-nuts, Aaron Bohannon

Constants in C are not untyped. 

Thomas

Philip Silva

unread,
May 29, 2012, 7:31:33 AM5/29/12
to golan...@googlegroups.com, Aaron Bohannon

The confusion people have with nil is that they don't realised that an
interface value is distinct from the value it contains. Which is the
result of an assumption that interfaces in Go are similar to
interfaces in Java etc.

In this regard Java handles things absolutely the same way as Go...  It's not obvious though because in Java all variables are pointers, with the exception of base types.

Ian Lance Taylor

unread,
May 29, 2012, 12:29:16 PM5/29/12
to Corey Thomasson, Aaron Bohannon, golang-nuts
Corey Thomasson <cthom...@gmail.com> writes:

> What statically typed language does not have untyped constants? C, C++,
> C#, and Java all certainly do.

That turns out not to be the case. Literals in C, C++ and Java are all
typed. (I am less familiar with C#, so I'm not sure.)

The difference is that C, C++ and Java all permit implicit type
conversions of numeric types. Go does not.

Ian

Corey Thomasson

unread,
May 30, 2012, 1:12:01 AM5/30/12
to Ian Lance Taylor, Aaron Bohannon, golang-nuts
Ahh I did not do my homework. C# also has typed literals. I apologise, but posit that untyped literals is simpler to grok than typed literals + implicit conversion.

Aaron Bohannon

unread,
May 30, 2012, 3:49:19 AM5/30/12
to Corey Thomasson, Ian Lance Taylor, golang-nuts
On Wed, May 30, 2012 at 1:12 AM, Corey Thomasson <cthom...@gmail.com> wrote:
> On 29 May 2012 12:29, Ian Lance Taylor <ia...@google.com> wrote:
>> The difference is that C, C++ and Java all permit implicit type
>> conversions of numeric types.  Go does not.
>>
> Ahh I did not do my homework. C# also has typed literals. I apologise, but
> posit that untyped literals is simpler to grok than typed literals +
> implicit conversion.

Implicit conversions I understand. I do not yet understand "untyped"
literals/constants, though. If they behaved as I might expect, then this
program would have a static error on either line 17 or 18 rather than on line
19:

http://play.golang.org/p/6gqKVVJRzi

The nice thing about implicit conversions (I never though I would be calling
them nice) is that they are fully determined by the _static_ types in your
program (correct me if I'm wrong). In contrast, the expression "nil" seems to
mean something different depending on the _dynamic_ type of the expressions
around it.

I can imagine describing Java's "null" as an "untyped constant", but in Java the
meaning of a comparison with null is statically determined, as is a comparison
of two objects that might be null. (Again, correct me if I'm wrong -- I have
trouble keeping the corner cases of every type system in my head at once.)

- Aaron

Thomas Bushnell, BSG

unread,
May 30, 2012, 4:26:59 AM5/30/12
to Corey Thomasson, Ian Lance Taylor, Aaron Bohannon, golang-nuts
Absolutely. This is one of the things that I think Go deeply got right.

Traditionally, you have typed literals and implicit conversion. In Pascal this doesn't cause so much of a problem, but in C, with the chaos around unsigned (and especially char), it's a nightmare. Languages like Scheme still have typed literals, but have a numeric stack which works by thinking about mathematical numbers instead of representations, so the problem is avoided that way.

Since Go didn't want to do mathematical numbers the way Scheme does, it chose an excellent alternative: use mathematical numbers for constants, and enforce strict typing and abandon implicit conversions. This is a brilliant solution, in my opinion, and for a language with typed variables, is the best of both worlds.

Thomas


Jesse McNelis

unread,
May 30, 2012, 4:33:37 AM5/30/12
to Aaron Bohannon, golang-nuts
On Wed, May 30, 2012 at 5:49 PM, Aaron Bohannon <aaro...@gmail.com> wrote:
> Implicit conversions I understand.  I do not yet understand "untyped"
> literals/constants, though.  If they behaved as I might expect, then this
> program would have a static error on either line 17 or 18 rather than on line
> 19:

The const are untyped, so they are converted to the types you assign them to.
This is very useful because making separate const for every type would
be quite annoying.
You'd need a different '3' for uint8,uint16,uint32,uint64 etc.

> http://play.golang.org/p/6gqKVVJRzi

'3.0' is the same as '3' if you assign it to an int.

> The nice thing about implicit conversions (I never though I would be calling
> them nice) is that they are fully determined by the _static_ types in your
> program (correct me if I'm wrong).  In contrast, the expression "nil" seems to
> mean something different depending on the _dynamic_ type of the expressions
> around it.

Nope, nil's meaning is fully determined by the static type of the
variable it's assigned to.
If you assign nil to a pointer the pointer will not point to anything.
Assign nil to an interface, the interface will not contain anything.
Assign nil to a slice and the slice will have zero len and zero cap
and point to no underlying array etc.

Your confusion is the result of you misunderstanding how interfaces work.
This might help, http://research.swtch.com/interfaces

> I can imagine describing Java's "null" as an "untyped constant", but in Java the
> meaning of a comparison with null is statically determined, as is a comparison
> of two objects that might be null.  (Again, correct me if I'm wrong -- I have
> trouble keeping the corner cases of every type system in my head at once.)




--
=====================
http://jessta.id.au

Aaron Bohannon

unread,
May 30, 2012, 12:44:31 PM5/30/12
to Jesse McNelis, golang-nuts
On Wed, May 30, 2012 at 4:33 AM, Jesse McNelis <jes...@jessta.id.au> wrote:
> On Wed, May 30, 2012 at 5:49 PM, Aaron Bohannon <aaro...@gmail.com> wrote:
>> Implicit conversions I understand.  I do not yet understand "untyped"
>> literals/constants, though.  If they behaved as I might expect, then this
>> program would have a static error on either line 17 or 18 rather than on line
>> 19:
>
> The const are untyped, so they are converted to the types you assign them to.
> This is very useful because making separate const for every type would
> be quite annoying.
> You'd need a different '3' for uint8,uint16,uint32,uint64 etc.
>
>> http://play.golang.org/p/6gqKVVJRzi
>
> '3.0' is the same as '3' if you assign it to an int.

That much I understand. But when I write this:

a := 3 + 3.0

I haven't written "int" or "float" anywhere. So I would expect Go to give me a
static error because a type cannot be inferred. OTOH, if Go does infer a type
for 'a', then I don't see how one can call 3 and 3.0 "untyped".

Consider this program:

http://play.golang.org/p/hIRUV7adRo

In what sense are the contants "untyped"?

> Nope, nil's meaning is fully determined by the static type of the
> variable it's assigned to.

I was talking about equality comparisons ("=="), not assignment statements.

This example makes it clear what I meant:

http://play.golang.org/p/vx4XLQwVrN

If the meaning of "nil" was determined statically, then I wouldn't be able to
get the function 'f' to print both true and false on the same call.

- Aaron

chris dollin

unread,
May 30, 2012, 1:01:46 PM5/30/12
to Aaron Bohannon, Jesse McNelis, golang-nuts
On 30 May 2012 17:44, Aaron Bohannon <aaro...@gmail.com> wrote:

>
> I was talking about equality comparisons ("=="), not assignment statements.
>
> This example makes it clear what I meant:
>
> http://play.golang.org/p/vx4XLQwVrN
>
> If the meaning of "nil" was determined statically, then I wouldn't be able to
> get the function 'f' to print both true and false on the same call.

The first argument of f is a nil interface, the second is a non-nil
interface whose value is a nil pointer, established by the static
typing in main:

var a V = nil

a is of type V (an interface) and you set it nil; it's an interface
value with noting in it at all.

var b *V = nil

b is of type *V (a pointer, not an interface) and that pointer is nil.

var i interface{} = a

A nil interface value (still).

var j interface{} = b

b is a *V pointer value, not any kind of interface. You put it into
an interface variable, which records its type (*V) and its value
(a nil). j is /not/ (an interface) nil; it records the type *V and the
value (a *V nil), as distinct from i, which records nothing. The
value b has doesn't matter for the conversion.

Then you compare the nil interface value, and a non-nil interface
value, against a nil interface value. Naturally one is true and the
other false.

All the nils types are statically determined.

Chris






--
Chris "allusive" Dollin

Ian Lance Taylor

unread,
May 30, 2012, 2:25:51 PM5/30/12
to Aaron Bohannon, Jesse McNelis, golang-nuts
Aaron Bohannon <aaro...@gmail.com> writes:

> That much I understand. But when I write this:
>
> a := 3 + 3.0
>
> I haven't written "int" or "float" anywhere. So I would expect Go to give me a
> static error because a type cannot be inferred. OTOH, if Go does infer a type
> for 'a', then I don't see how one can call 3 and 3.0 "untyped".

If the type of a variable is set from an untyped constant, the untyped
constant is converted to type bool, rune, int, float64, complex128, or
string respectively, depending on whether the untyped constant is a
boolean, character, integer, floating-point, complex, or string
constant.

In other words, although constants and constant expressions may be
untyped, they compiler does still have to track what sort of constant
they are. This is necessary not only to set the type in an example like
yours, but also to know what sort of arithmetic operation to use
(integer or floating point or complex). The details are in at
http://golang.org/ref/spec#Constant_expressions .

Ian

Aaron Bohannon

unread,
May 30, 2012, 5:03:35 PM5/30/12
to chris dollin, Jesse McNelis, golang-nuts
Sorry, I realize now that my example did not show what I thought it
was showing -- in fact, it showed exactly the opposite. It seems you
are right that the meaning of nil is statically determined. I guess
my confusion results from the fact that, in Go, assigning an
expression (e.g., the expression "nil") to a variable or passing it to
a function can change its dynamic type, whereas in most languages
assignment can only change an expression's static type. I know what
the implementation is doing, but no matter how long I stare at the
examples I write for myself, I still find the behavior extremely
counter-intuitive.

- Aaron

Aaron Bohannon

unread,
May 30, 2012, 6:08:44 PM5/30/12
to chris dollin, Jesse McNelis, golang-nuts
On Wed, May 30, 2012 at 5:03 PM, Aaron Bohannon <aaro...@gmail.com> wrote:
> Sorry, I realize now that my example did not show what I thought it
> was showing -- in fact, it showed exactly the opposite.  It seems you
> are right that the meaning of nil is statically determined.  I guess
> my confusion results from the fact that, in Go, assigning an
> expression (e.g., the expression "nil") to a variable or passing it to
> a function can change its dynamic type, whereas in most languages
> assignment can only change an expression's static type.  I know what
> the implementation is doing, but no matter how long I stare at the
> examples I write for myself, I still find the behavior extremely
> counter-intuitive.

...actually I think the think that makes it really counter-intuitive
is that the equality operator is also able to change the dynamic type
of "nil".

I suspect things would be quite a bit less confusing to me if the
language had no expression "nil" and instead had two built-in
functions: isNil() and resetToNil().

- Aaron

Paul Borman

unread,
May 30, 2012, 6:20:57 PM5/30/12
to Aaron Bohannon, chris dollin, Jesse McNelis, golang-nuts
Nil does not have a dynamic type.  Any occurrence of nil has exactly one type based on what it is assigned, cast, or compared to at compile time.  Nil by itself has no type.  It cannot change.

Jesse McNelis

unread,
May 30, 2012, 9:06:43 PM5/30/12
to Aaron Bohannon, golang-nuts
On Thu, May 31, 2012 at 7:03 AM, Aaron Bohannon <aaro...@gmail.com> wrote:
> I guess my confusion results from the fact that, in Go, assigning an
> expression (e.g., the expression "nil") to a variable or passing it to
> a function can change its dynamic type, whereas in most languages
> assignment can only change an expression's static type.

I think the implicit converstion to interface is confusing you.
Conversion to interface is the only implicit conversion that Go does.

var a int = 0
var b interface{} = a

is actually:

var a int = 0
var b interface{} = interface{}(a)

So when you pass something to a functions that takes an interface{}
the value you pass in is converted to an interface{} value

func A(i interface{}){}

A(5) // gives A() an interface{} that contains and integer of value 5

A(new(int)) //gives A() an interface{} that contains a pointer to
integer pointing to some newly allocated memory.

A((*int)(nil)) // gives A() an interface that contains a pointer to
integer that is nil.

A(nil) // gives A() an interface that is nil.


The important distinction between a nil interface value and a nil
pointer to some type is that you can still call methods on a nil
pointer to some type but you can't call methods on a nil interface.
A nil interface has no concrete type assigned to it so it doesn't have
any associated methods that could be call. A nil pointer to some type
still has it's type so methods associated with that type can be called
as long as they don't dereference that pointer.

This makes it useful to put a nil pointer to some type in an interface.
--
=====================
http://jessta.id.au

Aaron Bohannon

unread,
May 30, 2012, 9:32:46 PM5/30/12
to Paul Borman, chris dollin, Jesse McNelis, golang-nuts
On Wed, May 30, 2012 at 6:20 PM, Paul Borman <bor...@google.com> wrote:
> Nil does not have a dynamic type.  Any occurrence of nil has exactly one
> type based on what it is assigned, cast, or compared to at compile time.
>  Nil by itself has no type.  It cannot change.

Just to be clear: I was using the term "dynamic type" (for lack of a
better one) to refer to the type that an individual occurrence of nil
has.

- Aaron

Aaron Bohannon

unread,
May 30, 2012, 11:16:42 PM5/30/12
to Jesse McNelis, golang-nuts
On Wed, May 30, 2012 at 9:06 PM, Jesse McNelis <jes...@jessta.id.au> wrote:
> On Thu, May 31, 2012 at 7:03 AM, Aaron Bohannon <aaro...@gmail.com> wrote:
>> I guess my confusion results from the fact that, in Go, assigning an
>> expression (e.g., the expression "nil") to a variable or passing it to
>> a function can change its dynamic type, whereas in most languages
>> assignment can only change an expression's static type.
>
> I think the implicit converstion to interface is confusing you.
> Conversion to interface is the only implicit conversion that Go does.
>
> var a int = 0
> var b interface{} = a
>
> is actually:
>
> var a int = 0
> var b interface{} = interface{}(a)
>
> So when you pass something to a functions that takes an interface{}
> the value you pass in is converted to an interface{} value

Yeah, that's part of it. Basically, it boils down to this:

Everything in the language is designed to make it appear as if assignment to an
interface variable is creating an "is a" relationship....e.g., I can write:

var x int = 3
var a interface{} = x
fmt.Println(a == 3) // prints "true"
switch (a) {
case 3: fmt.Println("true")
default: fmt.Println("false")
} // "true" is printed

But it's actually creating a "has a" relationship. And the _only_ reason I need
to be aware, as a programmer, that it's creating a "has a" relationship is
because of the way the expression "nil" is handled The "nil" of the interface
type appears to "shadow" the "nil" of a reference type:

var x *int = nil
var a interface{} = x
fmt.Println(a == nil) // prints "false"
switch (a) {
case nil: fmt.Println("true")
default: fmt.Println("false")
} // "false" is printed

So, because of "nil", I need to be aware that a "has a" relationship is being
created when I assign something to an interface variable. Except....that's not
always true! It's not true when the thing I'm assigning to the interface is
another sort of interface type. Then the assignment really _is_ creating an "is
a" relationship:

type Foo interface{}
var x Foo = nil
var a interface{} = x
fmt.Println(a == nil) // prints "true"
switch (a) {
case nil: fmt.Println("true")
default: fmt.Println("false")
} // "true" is printed

Rationally, I know exactly what's going on in all these examples and how it's
implemented, etc, etc. But there's a part of my brain that will just never be
comfortable with it.

Since these "has a" relationships can only go one level deep, the "shadowing" of
nil has the appearance of being an unfortunate coincidence rather than the
outworking of some master plan. That is, if the language had simply used "null"
to refer to the zero value of reference types (other than interfaces) and "nil"
to refer to the zero value of interfaces, then there would be no opportunity for
this "shadowing" problem to happen. And, of course, I can hear people now,
saying "Two sorts of nil? That would be way too confusing! It's much cleaner
to just have one." But it's not cleaner! It's not simpler. Because now
programmers have to think about "is a" vs. "has a" relationships, implicit
conversion, untyped constants, etc, etc. What would have been an perfect,
elegant illusion is shattered by one detail that would have been trivial to
design differently.

Honestly, I don't know why it bothers me. Or why it took me so long to sort out
what I was confused about. Obviously, it doesn't bother everybody. And it
probably doesn't confuse everybody. But I don't know if it's because the flaw
in the illusion is so insignificant in most people's eyes that they just don't
notice it, or if it's because most people don't even care about programming in
languages that provide illusions and would rather just think about the guts of
what's going on all the time. (I have exactly the same question about C++
programmers, but Go is a dream-world in comparison with C++, so it's quite a
different question in that context.

- Aaron

Rob 'Commander' Pike

unread,
May 31, 2012, 12:22:17 AM5/31/12
to Aaron Bohannon, Jesse McNelis, golang-nuts
You're not the first to be confused. That's why I wrote this:
http://golang.org/doc/go_faq.html#nil_error

You're overthinking it. It's really very simple. Think of it
operationally rather than theoretically or by (misleading) example. I
know that approach has helped others to understand.

-rob

Mikael Gustavsson

unread,
May 31, 2012, 9:52:43 PM5/31/12
to golan...@googlegroups.com, Aaron Bohannon, Jesse McNelis
That's interesting how interface values are represented in Go versus other languages:
Whenever you pass around interface values in Go, you are passing around a (type,value) pair.
Whereas in Java, C++ etc.. you are passing around a pointer to an object. And that object would contain a hidden pointer that identifies it's type.

This allows us to for example do method dispatch on nil pointers(!):
http://play.golang.org/p/nK5ro8e1ty

I guess that this representation was chosen primarily to support Go's dynamic interfaces that replace traditional class hierarchies?

Kevin Ballard

unread,
May 31, 2012, 10:10:02 PM5/31/12
to Mikael Gustavsson, golan...@googlegroups.com, Aaron Bohannon, Jesse McNelis
On Thursday, May 31, 2012 at 6:52 PM, Mikael Gustavsson wrote:
That's interesting how interface values are represented in Go versus other languages:
Whenever you pass around interface values in Go, you are passing around a (type,value) pair.
Whereas in Java, C++ etc.. you are passing around a pointer to an object. And that object would contain a hidden pointer that identifies it's type.

This allows us to for example do method dispatch on nil pointers(!):
http://play.golang.org/p/nK5ro8e1ty

I guess that this representation was chosen primarily to support Go's dynamic interfaces that replace traditional class hierarchies?
Presumably it was chosen because, in the absence of interfaces, there is no type information at all at runtime. Since function dispatch is static rather than dynamic, there's no need for values to contain their types. So rather than try to put type information into all values, which would be a complete waste, it's just stuffed into the interface representation directly.

-Kevin

Mikael Gustavsson

unread,
May 31, 2012, 10:22:55 PM5/31/12
to Kevin Ballard, golan...@googlegroups.com, Aaron Bohannon, Jesse McNelis
On Fri, Jun 1, 2012 at 10:10 AM, Kevin Ballard <kbal...@gmail.com> wrote:

Presumably it was chosen because, in the absence of interfaces, there is no type information at all at runtime. Since function dispatch is static rather than dynamic, there's no need for values to contain their types. So rather than try to put type information into all values, which would be a complete waste, it's just stuffed into the interface representation directly.

-Kevin

Yes, that makes sense. C++ handles this problem by only putting in the pointer if the class has any virtual member functions (directly or by inheritance), in which case Go's approach is arguable much nicer.

Reply all
Reply to author
Forward
0 new messages