Comment by gnfargbl

1 day ago

As a long-time Go programmer I didn't understand the comment about two types of nil because I have never experienced that issue, so I dug into it.

It turns out to be nothing but a misunderstanding of what the fmt.Println() statement is actually doing. If we use a more advanced print statement then everything becomes extremely clear:

    package main

    import (
      "fmt"
      "github.com/k0kubun/pp/v3"
    )

    type I interface{}
    type S struct{}

    func main() {
      var i I
      var s *S

      pp.Println(s, i)                        // (*main.S)(nil) nil
      fmt.Println(s == nil, i == nil, s == i) // true true false

      i = s

      pp.Println(s, i)                        // (*main.S)(nil) (*main.S)(nil)
      fmt.Println(s == nil, i == nil, s == i) // true false true
    }

The author of this post has noted a convenience feature, namely that fmt.Println() tells you the state of the thing in the interface and not the state of the interface, mistaken it as a fundamental design issue and written a screed about a language issue that literally doesn't exist.

Being charitable, I guess the author could actually be complaining that putting a nil pointer inside a nil interface is confusing. It is indeed confusing, but it doesn't mean there are "two types" of nil. Nil just means empty.

The author is showing the result of s==nil and i==nil, which are checks that you would have to do almost everywhere (the so called "billion dollar mistake")

It's not about Printf. It's about how these two different kind of nil values sometimes compare equal to nil, sometimes compare equal to each other, and sometimes not

Yes there is a real internal difference between the two that you can print. But that is the point the author is making.

  • It's a contrived example which I have never really experienced in my own code (and at this point, I've written a lot of it) or any of my team's code.

    Go had some poor design features, many of which have now been fixed, some of which can't be fixed. It's fine to warn people about those. But inventing intentionally confusing examples and then complaining about them is pretty close to strawmanning.

    • > It's a contrived example which I have never really experienced in my own code (and at this point, I've written a lot of it) or any of my team's code.

      It's confusing enough that it has an FAQ entry and that people tried to get it changed for Go 2. Evidently people are running in to this. (I for sure did)

    • That's really my problem with these kind of critiques.

      EVERY language has certain pitfalls like this. Back when I wrote PHP for 20+ years I had a Google doc full of every stupid PHP pitfall I came across.

      And they were always almost a combination of something silly in the language, and horrible design by the developer, or trying to take a shortcut and losing the plot.

    • I believe you that you've never hit it, it's definitely not an everyday problem. But they didn't make it up, it does bite people from time to time.

      It's sort of a known sharp edge that people occasionally cut themselves on. No language is perfect, but when people run into them they rightfully complain about it

Author here. No, I didn't misunderstand it. Interface variables have two types of nil. Untyped, which does compare to nil, and typed, which does not.

What are you trying to clarify by printing the types? I know what the types are, and that's why I could provide the succinct weird example. I know what the result of the comparisons are, and why.

And the "why" is "because there are two types of nil, because it's a bad language choice".

I've seen this in real code. Someone compares a variable to nil, it's not, and then they call a method (receiver), and it crashes with nil dereference.

Edit, according to this comment this two-types-of-null bites other people in production: https://news.ycombinator.com/item?id=44983576

  • > Author here. No, I didn't misunderstand it. Interface variables have two types of nil. Untyped, which does compare to nil, and typed, which does not.

    There aren't two types of nil. Would you call an empty bucket and an empty cup "two types of empty"?

    There is one nil, which means different things in different contexts. You're muddying the waters and making something which is actually quite straightforward (an interface can contain other things, including things that are themselves empty) seem complicated.

    > I've seen this in real code. Someone compares a variable to nil, it's not, and then they call a method (receiver), and it crashes with nil dereference.

    Sure, I've seen pointer-to-pointer dereferences fail for the same reason in C. It's not particularly different.