> func(s *string) {
> // s maybe nil here, better check first
> }
If this happens, you don't have proper checks before this call. Clearly an error check was missed prior to this call.
> func(s *string) {
> // s maybe nil here, better check first
> }
If this happens, you don't have proper checks before this call. Clearly an error check was missed prior to this call.
Code that results in a nil dereference should not be compilable, period. Any programming language that allows it is flawed.
I think any language that allows that has different priorities than complete code correctness. There might even be programmers out there that would also prefer those priorities (simplicity, speed, etc) over having the compiler spend time every compilation to make sure a pointer is null or not.
It’s a clear no-brainer at this point: null references were a mistake, and any language with compile-time type checking is flawed if they’re allowed.
Maybe you mean 'dereferences', not 'references', because without NULL/null/nil, we can't interface to the real world which is filled with "there is no value here!" values.
Rust does not have null references. If I have a reference it is not null.
The "there is no value here" semantic is provided by Option<T> - if I mean that there may or may not be a reference I want Option<&T> an optional reference.
This gets repeated so much, so often, that at this point it's safer to assume that the person who is citing it is just so new to programming that they don't realise we've all already seen multiple times, almost always posted by an enthusiastic but ultimately green newbie.
> Rust does not have null references.
So? The word "reference" in Rust is defined by that languages specification, which is not the same definition as the word "reference" used in English, nor in other programming languages.
If you were having a Rust-specific discussion, you should have said so.
> - if I mean that there may or may not be a reference I want Option<&T>
And one of those options is `this reference does not exist`, which is what Tony Hoare referred to as a null reference in his writing(s).
Hence it's the dereference that's a problem in programming languages other than Rust, not the existence of the null reference in languages other than Rust.
Note, I am using the word 'reference' as defined everywhere outside of Rust. I am also using the word 'dereference' as defined everywhere outside of Rust.
I wouldn't have mentioned Tony's lecture in this sort of rant decades ago when I graduated because he hadn't given it. I would probably have cited his lament about Z (a formal notation, which I had by then learned) because some of the same lessons apply. New Jersey languages won for a long time, simplicity of implementation trumped correctness. Languages like C and eventually C++ dominated because while the programs were invariably wrong, the enormous cost of that could be somewhat justified by an insistence that better alternatives don't exist, even though of course better alternatives did exist.
But notice that even in C++ there are no null references. It's just that unlike Rust the C++ language inherits C's "strong typed, weakly checked" approach to types, it's trivial to make such an "impossible" thing, so although there are no null references, your references might be null anyway...
The whole point is the ergonomics, which you are ignoring whether out of ignorance or because you think you'll get away with it rhetorically. Option<&T> isn't a &T it only might be one. That's why C++ is probably getting std::optional<T&> at last in C++ 26, and why languages like C# add "non-nullable" types. Tony's mistake has terrible ergonomics.
Woah, there cowboy, you're mischaracterising my position.
When, and where, did I "insist that this was a good idea"?
Me pointing out that the real world is filled with null references is irrelevant to whether or not I think they're a bad or good idea.
> The whole point is the ergonomics, which you are ignoring whether out of ignorance or because you think you'll get away with it rhetorically.
Which bit made you think I'm ignoring it?
The sad fact of life is that programs are written to interface to the real world, which is filled with missing values. Regardless of the programming language you use, you'll still deal with them.
Some programming languages make it easier to deal with, and some harder. Regardless, you'll still have to deal with them, and IME the best way for a program to deal with the outside world is by doing Parse, Don't Validate at the system boundaries.
Since that is, and was, my position before this conversation took place, it seems to me that you've set up a strawman (i.e. my position is something other than what I now stated) so that you could destroy it to make your argument for Rust stronger.
In reality, if you're doing Parse, Don't Validate, then its really irrelevant if your language has compile-time-checked nullability types or not.
If you never make any mistakes then there's no value in systems which prevent mistakes. But you're human, so you do make mistakes, Most of us work in larger organisations in which there are many humans and they all make mistakes.
Parse, Don't Validate is very much a pattern in which it's crucial to avoid mistakes where maybe you don't have a reference and you need to care about that. In languages where the type system won't help catch that mistake it is likely to leak into production.
The other end of this is that people moan about the expense, and that's why I brought up Rust. With the Guaranteed Niche Optimisation Rust says that Option<T> is the same size as T where there's a niche, that means not only references, including the popular string slice reference &str, but many other types.
Rust's Option<OwnedFd> is exactly the same size in RAM as the C int would be for the same purpose (typically 4 bytes on a modern system). But your code won't accidentally try to read from a non-existent file descriptor in Rust, because instead of the int being this magic value -1, there is no corresponding OwnedFd, your Option<<OwnedFd> was None.
Wait, what?
Parse, Don't Validate is a way to make sure that invalid representations of data don't make its way into your system. Null is just one of many invalid data. Parse, Don't Validate does MORE than just prevent nulls, while the approach you are repeating only protects against nulls.
> In languages where the type system won't help catch that mistake it is likely to leak into production.
You are correct here: Languages with no enforced compilation step (Python, PHP, Ruby, Javascript) can't help even if you do do Parse, Don't Validate.
Languages with a compilation step and mandatory types (C, Go, Java, C++, etc) get the benefit of doing Parse, Don't Validate. While the benefit includes protecting against null dereferencing, it also includes protecting against other incorrect representations of the data.
In other words, if you're doing Parse, Don't Validate in Go, or C, or Java, etc, there's not much benefit, if any, to be gained from an option type. Once that $THING enters your system, it's already been transformed into the correct datatype and the compiler will ensure correctness, as far as type mismatches against null go.
In the languages you prefer (such as C), we can't distinguish this case from the case where we should always have a reference and not having one is a bug, they look the same, the machine can't help us get this right and hopefully the user carefully consults our documentation "NB this value may be NULL". Ugh.
Go is a wonderful example of a language that's grossly unsuited to this problem. Instead of "Make invalid states unrepresentable" Go chooses "Make representable states valid". Don't want a default for this type? Too bad, Go insists zero is valid and that's always your default, feel free to name this "DoNotUse_GoIsAWful" if you like, in a large system it'll get used anyway.
Of course. All compiler designers, outside a select few, are clearly wrong and have given the problem absolutely no thought.
https://en.wikipedia.org/wiki/Tony_Hoare
> At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement
So, it doesn't seem unlikely at all that other languages with type systems[0] probably just carried this behavior forward because it's how other languages worked at the time. The idea that you could have references which weren't allowed to be null probably seemed limiting, because other languages allowed it.
That this was a mistake is pretty broadly accepted at this point. It's not even a controversial statement any more.
[0] I'm excluding languages that don't have type checking at compile time (or without any compile time at all), that's a different discussion. I'm limiting the scope of my criticism to languages that (1) have a compile-time type checker but (2) opt to have that type checker allow null references to be used as if they're non-null.
If the answer is "Chicago" then "A City in North America" is low accuracy but "New York" is wrong.
This argument feels somewhat moot though. I cannot see how Go could ever reverse their nil decision because it’s so core to how interfaces work that I suspect it would end up being a massive breaking change.
In every language where pointers / references / values are not universally nullable, it's just an opt-in away, either as a wrapper type (Option/Maybe), a union type (T* | Nil), or a built-in language feature.
Either way you can pretty much always say "this may be nil", the difference is that you can't use a nullable value where a non-nullable value is expected, the language will require explicit checking (or in the worst case coercion).
That way once you've checked it's not null (somewhere you're not forced to panic ideally), you can pass around the pointer & be confident you don't need to ever check it again.