I've never used a language without null, besides C# nullables (which I also find perfectly fine, though agreed to be somewhat awkward). What is the alternative and its benefit? Null fits so many issues perfectly naturally, like a database entry without a value. And I find the C# '?' syntax to be amazing. For those unfamiliar with it:
string foo = someDb.GetRow(id)?.Value
foo is null when the row is null or the value is null, with 0 null pointer exceptions possible. If you wanted to allow someDb to possible be null, instead of intentionally crashing out, you could also add another ? after that reference. Really wish C++ had this. But in general I find null pointer exceptions to be desirable, because more often than not they indicate a failure of assumptions.
F# doesn’t have nulls by default and can completely interact with C# bybusi by Option types with the value set to None in case of null and Some<T> when it actually has a value.
This is so much more explicit and composes so much better than C#’s null.
If I declare a variable with a certain type in C#, its type is implicitly set to that type and null. I am allowed to call every operation that is supported by that type on the variable, and yet, it’s actually a lie because if the variable is set to null the purportedly supported operation won’t run and will throw an exception.
In F#, however, if I declare something of a certain type, it’s guaranteed to be of that type. Any operations on that variable supported by that type are guaranteed to run.
And if something might indeed not have a value (say a database result) I have to explicitly declare it as an option type so there is a None option for the value. This way the F# compiler will ensure I’m handling the None scenario properly and don’t end up calling unsupported operations on Null.
This is a much better design. I in fact struggle to understand why anyone thought it was a good idea to allow someone to declare a certain type but create an object which is not actually that type but is implicitly Type | null.
It makes sense in an unmanaged language like C, but makes no sense whatsoever in advanced languages like Java or C#.
I much prefer languages without Null (Rust being a personal favorite). I'll speak to that because I have a bit of experience with both C# w/ Nullable and Rust.
In Rust, you express types using the Option syntax. Its basically saying "This value could be None". It behaves similarly to nulls, but instead of Option being implicit (like C# nulls) its explicit. You can't do `let foo: String = None` because its an invalid type assertion. Whereas you can do `let foo: Option<String> = None`. In C#, you can't express "This is a string that cannot be null" (even with nullables if I recall correctly).
Sibling comments mentioned Option types, which are assumed the currently best approach. I agree, but would like to add Python as a different example.
Null handling in Python is actually sane, despite there being no Option type.
Its Null is called None, which is of type NoneType, which sits at the very top of the type hierarchy (inherits from object). That’s it. Everything else is separate from it. You perform a check for None, and after passing, you are guaranteed no null reference. Dereferencing cannot blow up.
A variable of type “Union of None and SomeClass” is safe to use after a None check. Type checkers will flag missing checks as errors. This is in contrast to C# or Java, where any reference type can end up in a rug pull and cause a null dereference.
In Python, assuming a type checker, checks are mandatory and cannot be forgotten (quite similar to Option types actually). In the other languages they’re not mandatory and can be forgotten (by default).
> Sibling comments mentioned Option types, which are assumed the currently best approach
Option types are actually just the tip of the iceberg though. The real power is in languages that support algebraic data types. Option is just one common example, used to distinguish between just Some and None, but it's reasonable to have an enum with other states, each optionally with its own associated data.
Imagine if Nullable<T> allowed ref (class) types, and then disallow ref types from actually ever being null, and you have an approximation of the alternative: option/maybe types. https://en.wikipedia.org/wiki/Option_type
I absolutely did not get it until I really used it. It's 10x the sensation of fearlessness that C# non-nullable ref types give you. For example, you can't do something like this:
var customers = new Customer[10];
(If I'm remembering correctly, C# in nullable mode allows this - and it's incorrect). You actually have to give it 10 instantiated Customer, or do:
var customers = new Option<Customer>[10];
Then, once you've filled it:
var filled_customers = customers.Select(x => x.Unwrap());
Now what's especially cool is that these are often treated like Lists with a maximum count of 1. What does that mean?
var customer = new Nullable(new Customer());
customer.Select(x => Console.WriteLine(x));
customer = Nullable.Empty;
customer.Select(x => Console.WriteLine(x));
That only does something in the first Select, the second is like an empty list. When you start thinking this way, you can do stuff like:
customers.SelectMany(x => x);
So, keeping in mind what SelectMany does (it essentially flattens a list of lists into a single list), you'd filter out all the unallocated customers. Think about all the stuff you do with Linq (including the Linq syntax), and how nice your code would get if you could just treat null as a list of length 0. It's super-neat, ? on steroids (? does win in the brevity department though) and once you learn the mindset [generally good] code just flows out of you. It's like the Matrix, you have to see it for yourself to understand it. It teaches you a new way of thinking.
Please go take a look at Rust and/or Swift to see how nice a language without null feels like.
It’s not that there’s no way to model nullability/optionality. It’s just that there’s a much better way to do it. Simple, obvious, composable, no quirks.
string foo = someDb.GetRow(id)?.Value
foo is null when the row is null or the value is null, with 0 null pointer exceptions possible. If you wanted to allow someDb to possible be null, instead of intentionally crashing out, you could also add another ? after that reference. Really wish C++ had this. But in general I find null pointer exceptions to be desirable, because more often than not they indicate a failure of assumptions.