It was fine to design a language with nullable pointers in the 70s. It's unacceptable nowadays. nil in Go is a major mistake.
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.
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.
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.
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.
that would need dynamically typed data...
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).
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.
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.]
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.
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.