
Having fun with Go's nil, interfaces and errors - katcipis
https://katcipis.github.io/2016/09/17/fun-with-nil-interfaces.html
======
dmcg
Is this confusion just so that we can ask the type of a nil interface value at
runtime?

A man orders coffee in a restaurant.

"Would you like it with or without cream?"

"Without"

A few minutes later the waiter returns.

"I'm sorry sir, we've run out of cream. Would you like it without milk
instead?"

------
feklar
Nil interface/nil pointer in the interface errors also explained in this 'Go
landmines' post
[https://gist.github.com/lavalamp/4bd23295a9f32706a48f](https://gist.github.com/lavalamp/4bd23295a9f32706a48f)

~~~
katcipis
I already had a few surprises with the for loops + closures too :-). Thanks
for sharing.

------
junke
> This is a very interesting situation (and not a very common one for me)
> because it is where Go’s simple errors as values decisions shines. Doing
> this kind of thing with exceptions would be pretty clumsy, [...]

Just because a language supports exceptions does not mean you always have to
use them. You can always use a helper function to catch any error and return
it as a value.

~~~
fizzbatter
Wouldn't that basically be replicating a return value-error approach, in a
more cumbersome way though?

You could argue that Go can use Exceptions in the same manner as X lang too,
by using panic and recover... but most people seem to not like that.

With Go and Rust, return value error handling is just simply nice. There's no
question about if a function might error out, just look at the signature.

~~~
junke
> Wouldn't that basically be replicating a return value-error approach, in a
> more cumbersome way though?

This would not be "replicating" anything, exceptions already are values in
most languages. The only thing to do is wrap your call with a function, like
"err = catch-all(expr)".

> ou could argue that Go can use Exceptions in the same manner as X lang too,
> by using panic and recover...

Except that it would be cumbersome ;-) because panic/recover works only in
combination with defer[0].

> With Go and Rust, return value error handling is just simply nice.

Using non-exceptional control flow for errors can be useful depending on the
circumstances. Using exceptions is simply nice in many other cases.

> There's no question about if a function might error out, just look at the
> signature.

Just look at the documentation. Code defensively.

[0]
[http://stackoverflow.com/q/3413389/124319](http://stackoverflow.com/q/3413389/124319)

~~~
junke
Just to clarify:

    
    
        err = catch-all(expr)
    

If your language does not provide a similar tool, you can't just use a
function because "expr" is evaluated before the function is called. I had Lisp
macros in mind when writing this. A poor man's approach that is still generic
is to wrap the expression in a closure:

    
    
        err = catch-all(() -> expr)
    

Not too cumbersome, but YMMV.

------
SirWart
I got bit by this the other day when returning a nil pointer to a struct as an
error. It was incredibly frustrating when I finally figured out what was going
on, and I'm curious if there's a good reason to distinguish between these 2
types of nil or if it was just an oversight/mistake during the language design
process.

~~~
catnaroek
Most likely the reason is keeping the language implementation simple. Java-
style `null` is actually quite subtle:
[http://stackoverflow.com/a/2707333/46571](http://stackoverflow.com/a/2707333/46571)

~~~
pcwalton
Go has the same complexity. From the Go spec: "For an expression x of
interface type and a type T, the primary expression x.(T) asserts that x is
not nil and that the value stored in x is of type T.'"

------
karmakaze
I had run into the same situation of handling multiple errors. Assuming I
wasn't the first to need such a thing I found multierror[1]. Searching now
returns more choices some of which use a nil []error and some with structs.

[1] github.com/joeshaw/multierror

------
zalmoxes
Francesc Campoy covers nil across all language types in Go
[https://www.youtube.com/watch?v=ynoY2xz-F8s](https://www.youtube.com/watch?v=ynoY2xz-F8s)

~~~
katcipis
I mentioned this presentation on the beginning of the post, it is a great
source of information on nil behaviour.

------
benguild
Even Google's App Engine SDK has its own variant of the "multi-error" ... like
an array of errors in one.

------
karma_vaccum123
I love the idea of a "read only" map (or any other type), but it seems it is
just a side-effect landmine in this case. Yuck...almost strikes me as a bug as
described in the article

~~~
barsonme
A map is a pointer to the actual map implementation. When declared via

    
    
        var m map[T]T
    

you're basically writing

    
    
        var m *runtime.hmap
    

which is a nil pointer. Go has sane zero values, so it makes sense that
reading from a nil map returns the zero value for whatever type its values
are.

However, there's a trade-off: either Go's maps allow nil assignment (in a
similar fashion to slice's `append') _or_ it retains its "reference" (forgive
me for using that term) semantics.

Go's designers decided that maps should differ from slices in that regard, and
I tend to agree. I think it'd be a mistake to have to always return a map
_just_ in it's newly allocated.

------
catnaroek
> Why ? It seems to me that it happens because how interfaces are implemented

Why on Earth should a user of a _high-level_ language have to care about
language implementation details?

> What the actual fuck ???

Exactly.

~~~
Vendan
Except it's not a language implementation detail, it's part of what the
language _is_. You might as well ask why a user of another language needs to
know what a class is, or how inheritance works. Too many people seem to be
assuming "Oh, it's like void*, so it must act like that always!", but it's
not.

~~~
catnaroek
> Except it's not a language implementation detail, it's part of what the
> language is.

Okay, but what was the rationale for designing the language like that?

~~~
jerf
The last bit of information you need to understand that is that unlike some
other languages you may be used to, "nil" is a perfectly valid value for a
pointer to have. You can write methods like this:

    
    
        func (o *Object) ReturnSomething() int {
            if o == nil {
                return 0
            }
            return o.SomeOtherValue
        }
    

It is also therefore legal to return in an interface value a nil pointer to a
struct of a particular kind, and therefore "an interface containing a nil
pointer of a particular type" and "an interface containing nothing at all" are
fundamentally different things that can not be collapsed together.

So as Vendan says, it is just part of the language. All languages have this
sort of wart in them, where two or more perfectly sensible decisions interact
to create something that isn't sensible at first glance.

(I'm still in favor of going back in time and changing Go to have non-nillable
values, but that ship has certainly sailed.)

~~~
catnaroek
> unlike some other languages you may be used to,

The languages I'm used to don't even have `nil`.

> "nil" is a perfectly valid value for a pointer to have.

So, just like C and Java. I don't see the difference.

> You can write methods like this: (snippet)

Oh, now I get it: `nil` itself isn't a value. `nil` is syntactic sugar that
expands into a nil value. (In other words, confusingly enough, `nil` and “nil
value” are different things!) This is unlike Java, where `null` itself is a
value.

It would be very helpful if people described things precisely.

> All languages have this sort of wart in them, where two or more perfectly
> sensible decisions interact to create something that isn't sensible at first
> glance.

This means that at least one of the two (or more) decisions was less sensible
than it originally seemed. Good design decisions don't introduce warts into a
system.

~~~
Vendan
nil doesn't "expand", it's just what it is. You have to bear in mind that
methods are roughly syntactic sugar from

    
    
      func (o *Object) ReturnSomething() int
    

to

    
    
      func  ReturnSomething(o *Object) int
    

In the first case, it may "seem" that a nil Object would be invalid, but in
the second case it's obviously valid, and makes complete sense. It may be
described as a "wart", but it makes complete sense to me. The issue is, in my
opinion, that people drag their own expectations into programming languages as
they learn them.

~~~
pcwalton
In this case, Java and C# have the same feature and don't have this behavior
around null. I think it's reasonable for people who have seen identical
features in other languages to expect similar behavior when learning new ones.

~~~
Vendan
They don't have the same feature of "I can call a method on a null value", at
least, not that I've seen... You are calling them identical features when the
fact is that they aren't. C# and Java both have generics, do they perform
identical? Haskell has generics too, but I bet they'd throw a shit-fit if you
tried to say "Oh, C# has generics too, so why would I bother with Haskell?"

~~~
pcwalton
The ability to call a method on a nil receiver doesn't have anything to do
with what comparisons should do. Semantically, the question is whether ==
should compare the values or the vtables. In no other language I know of does
it compare vtables.

~~~
slrz
Interface comparisons check both, the value and the dynamic type. Describing
that as "comparing vtables" is misleading.

It the types don't match, the operands are not considered equal. Type _and_
value have to match for two interface values to be equal. How does that not
make sense?

For mismatched dynamic types you can't meaningfully compare the values in the
general case, so of course you need to consider types.

~~~
pcwalton
> Type and value have to match for two interface values to be equal. How does
> that not make sense?

Because people expect == to compare values, not types. Nobody has ever
questioned this behavior in Java and C#.

