You're replying to someone who is claiming to come from typescript where nullability must be expressly opted into and once opted into must be branched upon without use of a monad (isomorphism notwithstanding). Kotlin would be another example of that. Even with python type hints the same thing can be achieved or with Java and annotations. The problems in all of these cases is the boundaries between the languages where the nullability or lack thereof is often just assumed and not asserted upon.
There are language decisions other than options that can work, but they boil down to the same thing: force a branch before use. If go does not have this sort of secondary type system now, it won't be too long before someone sees the value.
> This applies to all languages with nil. The alternative being options (like ocaml)
It actually doesn't. Languages like Kotlin, C# and TypeScript special case null in the type system and allow you to specify whether or not a given type should include null or not.
Not entirely true - e.g. Kotlin has null safety built into its type system, and I find it much nicer than the option/optional approaches used in various other languages.
That’s not the same thing as a nil pointer though.
Go data types cannot be null but a pointer to a data type can be. And what a nil pointers is, is a valid type of pointer but which points to an invalid point or memory.
Typescript doesn’t have the same problem because it doesn’t have pointers.
Rust somewhat this problem with its borrow checker but it’s still entirely possible to create a nil pointers in Rust and without using ‘unsafe’, but admittedly it’s not by writing idiomatic Rust.
Go really should do more to prevent issues here but the real problem is around the creation of “objects” (since Go isn’t fully OOP) and how you can’t really create an image struct (nor even map) and have it default as empty. I’m sure the logic behind that is for performance, but why not expose the option of an uninitialised struct pointer behind ‘unsafe’ for those who are willing to accept that risk?
Nil pointers aren’t the same thing as optional types in python.
You can achieve the same thing as optional types in Go using ‘…any’ and the standard library has made use of that pattern since the very first release of Go.
Nil pointers in Go are a completely different problem and there isn’t really a direct comparison in dynamic languages like Python nor Typescript because they use references rather than pointers.
I don’t think there is a difference between go pointers and references in python. For normal pointers to structs at runtime I suspect the implementation is very similar. Maybe it’s different for typescript because the compiler can protect you from null references statically and I assume if you are using python types the python type checker can also protect you at compile time.
A nil pointer isn’t a type. It’s a pointer to a type and that type hasn’t been created. In short, it’s an initialisation problem rather than a type system problem.
Where “references” differ is you have to initialise the type to get a reference. In Python and Typescript, you don’t create naked pointers. You create structs then pass that reference in functions.
The problem with Go is that you can create those naked pointers and then you need to remember to create your struct and then point that pointer to the struct. And you’re sometimes you’re incentivised to write code this way because Gos garbage collector needs to do more work than if you create pointers adhoc after (like references). But you can use pointers like references if you wanted too.
So there isn’t really a direct comparison with languages like Python and Typescript because they don’t have the same primitive to begin with.
There are benefits to Gos approach: you get more control over memory usage as well as improved performance. But like any idiom, it comes with disadvantages too. And the reason Go panics is because the alternative is the risk of silent memory corruption, which is what a lot of other languages can suffer from with pointers. Though this isnt to say that there are also languages that do solve this problem in a much better way too. But they’re are also a completely different language to Python and Typescript again too.
So the comparisons to Python and Typescript simply don’t work for this type of problem.
Actually there is. In your first example you’re passing an object as a reference, and in your second example you’re creating an empty pointer with no object attached.
A better example will be:
foo := Foo{}
useFoo(&foo)
That go code would be functionally equivalent to your Typescript code and if everyone used pointers like references in Go, like the above code, then you wouldn’t have any nil pointer bugs.
Nil pointer bugs happen when you need to use pointers as pointers. (Shock horror!) And sometimes you do need a pointer with no object attached when solving specific problems are aren’t well suited for Typescript.
Python and Typescript doesn’t have pointers. Those languages made different trade offs so they lose performance and memory management controls as a result.
It’s no secret that Python needs to call FFIs to (for example) C++ code when performance and memory management concerns arise. Likewise with node too. In fact I’m in the process of debugging a 3rd party Rust library for Node that’s not handling pointers correctly and thus causing sporadic crashes of the node runtime. And that Rust library only exists in the first place because JavaScript doesn’t have primitives to write the required code natively in node at the performance required for scale.
Maybe WASM will be the saviour here. But it feels like we are now just piling on more abstractions between the code and CPU rather than trying to learn how to use our existing set of tools better.
There are language decisions other than options that can work, but they boil down to the same thing: force a branch before use. If go does not have this sort of secondary type system now, it won't be too long before someone sees the value.