Preferences

This tidbit gets a ton of mileage but I think it's overrated. There are a lot of unsafe shortcuts we take to get better ergonomics and NULL is one of them.

I think it's a bit unlikely we'll fully get rid of null, but we can get rid of some of the pitfalls. TypeScript for example pretty much fixes the problem, by enforcing you check for null when needed, though TypeScript takes a handful of other soundness shortcuts. Go makes null less harmful by treating nil pointers like empty values by convention.


It's the worst mistake because it made you believe that its atrocious ergonomics are actually superior to more sensible solutions. Implicit nullability doesn't really save you any null checks. It just makes it possible to forget necessary checks.

It was fine to design a language with nullable pointers in the 70s. It's unacceptable nowadays. nil in Go is a major mistake.

Okay. So let's say we get rid of nil in Go. Now, structs with pointers have no zero value. Slices and maps have no zero value. Funcs have no zero value. Reflect can no longer create objects because it can't possibly enforce that you initialize the pointers. Functions that return either an error or a value now need a new pattern, probably requiring generics or another special type. Map access needs to return this special type.

Did we win? Did that make Go better? Fuck no. Most people aren't frequently hitting nil pointer errors in Go because unlike C the behavior is a lot more reasonable and the conventions a lot simpler. And by the way, we didn't fix all the runtime errors. Nil pointers are just one possible runtime error. How about out of bounds array access, memory exhaustion, race conditions?

And yeah, I get that you can also fix all of those things, which is then called Rust. But we don't need another Rust, Rust is a fine Rust. Go has, imo, much better ergonomics and most of the time it's just fine for what I'm doing. Like, writing small to medium size servers and utilities in Go has rarely been a regretful experience. And, even if we had no runtime errors we would still need unit testing to ensure our components are functioning correctly. So, most of the time I'm aware of when my code has runtime errors anyways.

Getting rid of null is not magic. It does not get rid of all runtime errors. And yes, it does impact ergonomics. I will take Go zero values at the cost of nil pointers, every day.

> Now, structs with pointers have no zero value.

A zero value is much better than undefined value, I'll grant you that. I prefer the forced initialization approach (Haskell, presumably Rust and many others). If I add a new field, I want to know where I need to populate it. Or if you must, maybe a default value defined on the struct (perhaps that's also "considered harmful" for reasons I can't think of at the moment).

But it seems you prefer the ergonomics of default-zero. I don't get it, but I can't argue with preference.

Easy: default zero is simple. It's predictable behavior. It's consistent.

By convention, you should design your code to also treat zero values as empty. In Go, the zero value of bytes.Buffer is a ready to use, empty buffer.

If you drop default zero, you lose a lot of convenience and gain a lot of ceremony. It's not the end of the world, but neither is the null pointer error. It's just another runtime error. Just like divide by zero.

> Implicit nullability doesn't really save you any null checks.

It sort of does, because explicit nullability forces you to do many redundant null checks when you actually knew that something could not possibly be null.

If you know something can't possibly be null, then store it as a non-nullable type as soon as you know that.
> as soon

that would need dynamically typed data...

I totally disagree - NULL gives much worse ergonomics all-around. While modifying code, I'm constantly afraid of whether the value I'm accessing could be NULL. Most SQL schemas are filled with "NOT NULL" to the point of ridiculousness, and most Java methods that I've seen tend to have @NotNull used everywhere too. Not having NULL gives you a lot of confidence when reading and writing code, by guaranteeing that your object does indeed exist.
I would recommend looking Haskell's Maybe and Rust's Option type to get a better idea of how this can be solved -- and how this article isn't really overrated (just commonly misunderstood).

They allow for explicit NULL-ness (which is a necessary concept) without falling into the trap of making everything implicitly possibly NULL. And when NULL-ness is explicit you are then forced to explicitly handle it in order for your program to compile (in the case of Haskell and Rust).

I dunno why everyone's assuming I don't know about the common solutions to the problem. TypeScript does explicit nullness without needing monads, and I actually mentioned that one. I still disagree that this is not overrated.

Go's idea of nil, for example, seems OK to me, and the language would need to be way more complex to fix it. For example, it would need a type system with explicit nullness, or maybe even actual generics. But it mostly doesn't matter because doing things with nil doesn't crash nearly as much in Go. Like a nil slice just acts like an empty slice. You can even append to nil and it returns a non nil slice. You can call methods on nil. Etc.

The trouble with getting rid of nil to me is that it requires you to either have values at all times, or deal with the possibility that you don't at all times. Go has the very very nice property that you can initialize any type to a zero value and it should work as an "empty" object. Pointers without nullability don't have a zero value. Fixing nulls at the cost of getting rid of Go's properties for zero values would not be worth it.

Don't get me wrong, Go does nil a lot better than some other languages (being able to call methods on a nil is sometimes a good thing depending on how your methods handle it -- most methods don't handle it well at all). The fact that even most map operations (access and deletion) also "just work" is really useful.

But I think you're over-selling the zero values feature of Go. It is very rare to see third-party libraries that have zero values which do anything but cause a NPE when you try to use them -- mainly because they embed pointers and then you have the same implicit NULL-ness that causes NPEs everywhere. It is great that the core language managed to get zero values right in most cases, but it's far from being as wide-spread as you might hope.

Also (nil interface != nil pointer that fulfills interface) is a very common mistake I see in Go code, and while it's not necessarily related to the existence of a nil value it is still related to the general concept of nil in Go.

[And on the TypeScript comment -- you don't really need monads for Option<> or Maybe types. You just need algebraic types -- and TypeScript has those. Haskell does use Monads for Maybe, but that's because Haskell has many other type-theory things that make it necessary to support using Maybe as a monad.]

I'm not overselling zero values in Go. Simply try to envision the cascading consequences on the language if you removed the zero values; no existing Go code would work, and I think the language would need to shift so much that even hello world couldn't be automatically translated to such a language. All to prevent a single type of runtime error among many, one that Go developers are not complaining about the way that Java, JavaScript, C developers have.
Nobody is arguing that Go should have algebraic types and ditch zero values, so I don't know why you're harping on this point. Now -- it would be somewhat nice because errors would be much more reasonable to handle (the new "check" proposal is okay but still quite flawed) but you're right that it would either be far too complicated or old code wouldn't work anymore. Go has already made it's bed when it comes to nil values, but that doesn't mean that all new languages should follow suit -- because Go's nil handling isn't all sunshine and roses (nil interfaces -- for obvious reasons -- cause NPEs).

As I've said, Go does nil basically as well as you can without having algebraic types. But given the semi-anecdotal evidence that I've definitely seen my fair share of NPEs in production Go code in the past 5 years, it's clear that it's not sufficient.

But nobody's talking about "getting rid of nil" wholesale. They're talking about getting rid of null/nil reference exceptions via the mechanisms of non-nullability (having the option to define some variables as non-nullable) and null guards (a compiler which forces a null check before any operation which requires a non-null value). This way you still have null, but with compile-time guarantees that you'll never get null when you didn't expect it.
That's the same as the TypeScript approach, but its never going to happen in Go because of the relatively small gain for a massive jump in complexity in the compiler.

This item has no comments currently.

Keyboard Shortcuts

Story Lists

j
Next story
k
Previous story
Shift+j
Last story
Shift+k
First story
o Enter
Go to story URL
c
Go to comments
u
Go to author

Navigation

Shift+t
Go to top stories
Shift+n
Go to new stories
Shift+b
Go to best stories
Shift+a
Go to Ask HN
Shift+s
Go to Show HN

Miscellaneous

?
Show this modal