"For Dart, we chose the path of sound null safety."
Null Safety is the killer feature of modern programming languages like Swift and Kotlin. It provides a clarity you just can't get otherwise and saves you from runtime errors. And it requires very little additional ceremony to use.
While I love null safety, It's hardly a given. For Dart in particular, it sounds like there was a serious discussion about the choice, and it could have gone the other way.
(edit: removed incorrect statement about Python's type safety)
FWIW my (1 year old) experience with mypy was very poor. Stuff breaking between releases, wrong resolution, bugs. It kinda felt like a 3rd party fun project, not an official high quality solution.
The fact that the source can contain completely wrong declarations and Python will happily run it anyway felt really bad. It's kinda like JavaDoc rather than a classic type system.
Languages like Java, Go and C were all designed before it became obvious that `Option<>` or `?` were obviously the right thing to do.
You can live with it, but most language eventually add some way to deal with it. Java has @NonNull. Javascript and Python both have static type annotations that are non-nullable. Even C++ has some attempt at it (std::option<>).
The only one I know of that hasn't bothered trying is Go.
But only to ML and Haskell programmers. Not it is obvious to basically everyone. (Excluding people who just haven't discovered static typing at all yet.)
Zero-values and pointers-as-options is not even half as good as Option<T>. At best, it was easy to implement, and convenient to use, until you actually had to run the program.
I dunno, we had a terrible experience with Go nils at my current job:
- It's super-easy to create a situation where some some obscure code fragment can cause a panic and crash the whole app due to nil pointer access, especially in multithreaded scenarios
- We had to start using linters to track possible nil errors early on
- Eventually we just moved to Kotlin, where this is not an issue
- And don't get me started on nil interfaces, good luck properly checking nilability and tracking these kind of issues
I mean all of those things are downsides to having unsafe nullability. However, my point was that those things obviously aren't deal breakers for many, many people as Go is still extremely popular.
I feel null isn't the real problem, the problem is the lack of semantics attached to null.
NotNull, or so called "null safety" makes it that when you know something is not optional, if a null is passed to it, you know it's a bug.
But when you do have null again, by making something nullable, the million dollar problem is back, in that you don't know why it's null?
Is it null because it's missing, false, errored, eof, leaf node, etc. You don't know.
In my opinion what we would need is union types, and simply never use null for anything, remove it completely.
Instead you could declare a function returns a Value or Missing. Or it would return a Value or False. Or it would return a Value or EndOfFile.
And similarly you could say:
User {
String|NotProvided email;
}
Where email on user can be either a String or NotProvided.
You wouldn't want the NotProvided, EndOfFile, and all that to be full on classes though, just like type aliases of some sort, similar to null in a way, but it adds semantics.
if (email == NotProvided) {
...
}
And the language compiler would error if you try to use it where it doesn't work without checking:
String email = user.email; // Error
if (user.email != NotProvided)
{
String email = user.email
} // No error
That's exactly why I love Rust.
Of course, if you don't want to write good Rust or are lazy, you can "reimplement" null with the Option enum and unwrap() it everytime, so your code panics everytime, or do the same by returning errors without thinking about if your code should move on. You still have to think about it, but at least you're not doing null checks AND Option.NONE checks, which is extremely tedious in Java, for instance.
So, for a quick script or rapid prototyping, just unwrap/panic if you don't care about the result, like in a short code contest, and for any other use, refrain from using these, match all possibilities, implement a strongly typed Error management.
basically that's how it works in Kotlin (and Dart and others) already. String? is String|null and it's a compiler error to reference a member without a prior null check.
In Kotlin (and others) it's extremely ergonomic, too. You just say foo?.bar and coalesce with ?: so
val baz = foo?.bar ?: "foo or bar is null"
In certain cases the Kotlin compiler will infer non-nullable type after an explicit null check just like in your example such that
fun bestFunction(foo : String?) {
if(foo == null) return
foo.baz <- //now legal to call without .?
}
Not really. The gp is complaining about the lack of semantics about what a missing value means. `String?` still doesn't tell me what not having a string means. `String|NotProvided` or `String|NotFound` or `String|InvalidInput` all have the same functionality as `String|null` but tells me something about what the missing value is indicating.
I mean, generally I would love (more) union types, unfortunately so few languages have them.
For Ops requirement I think it's too specific for a language level feature. In Kotlin you'd just use a sealed type in the cases were you really need to know why function can't produce a value. Works like a charm.
I don’t know, it is quite trivial to statically analyze nulls, so even with basic IDE analysis I don’t even remember the last time I got an NPE in Java. To put such a basic thing as a “killer feature” doesn’t make for a good advertisement :D
I mean... the type checker is a static analyzer. Why not bake the most commonly seen type error into the language's type checker?
For all of its flaws, at least C++ made null references undefined behavior. Coming from (pre-NotNull) Java, it's nice to avoid lots of defensive null-check boilerplate.
Static analysis can go only so far. The Intellij has been helpful, but far from sufficient. One of the fundamental problems is also that static analysis won't help you differentiate between an intent (the method should not return null) and a mistake (it returns null anyway).
@NotNull/@Nullable is not part of the standard lib, but is a de facto standard nonetheless. I found that defaulting to everything non-nullable (there is a setting in these tools which should be the default, implicit nullable or non-nullable) and providing the few places where you want to use null explicitly gives you effectively the same null safety as Kotlin (note that the standard lib is annotated by these tools)
> @NotNull/@Nullable is not part of the standard lib, but is a de facto standard nonetheless.
Which one is the standard, the dormant JSR-305, the IntelliJ annotations or the checker framework (I guess that's the best candidate since it was actually accepted)?
> I found that defaulting to everything non-nullable (there is a setting in these tools which should be the default, implicit nullable or non-nullable)
Does this have a good cross-IDE support?
> gives you effectively the same null safety as Kotlin
"effectively", just ergonomically way worse.
But the main issue I have with this is that it requires you to actively seek null safety, pick a particular "standard" and push it through, ruthlessly enforce it during a code review. Which mostly doesn't happen. I've never seen a large codebase (which I would work on) which would use such annotations consistently. Yet unsurprisingly, all Kotlin code bases I've seen were in fact null safe.
> Which one is the standard, the dormant JSR-305, the IntelliJ annotations or the checker framework (I guess that's the best candidate since it was actually accepted)?
Hence “de facto”. All of these tools understand all of them.
And I do have to agree with your last paragraph, it is not as good as native support, but nor is it as big of a pain point as it used to be/as some people think it still is.
> I found that defaulting to everything non-nullable (there is a setting in these tools which should be the default, implicit nullable or non-nullable) and providing the few places where you want to use null explicitly gives you effectively the same null safety as Kotlin
Not really, I'd say not even close. In Java you would be trying to avoid nulls whenever possible, but in Kotlin nulls elegantly become a flow control feature.
> Well, try to minimize the usage of nulls in your programs then :D
I am a java guy since 1.1 and it seems to me that a lot of problems in Java come down to it being designed for a time when a application mainly consisted of in-house / in-org / self-written code. In $currentYear, however, we mostly plug libraries into frameworks and it's just a pain to check every call and every return value for the possibilities of null. Yeah, it can be done but I just don't like having to read library source code to find out if it will return a null in some cases.
Open-source libraries were much less common when Java first arrived, but third-party C / Fortran libraries were very common in many domains much before Java arrived.
Nulls, arrays degenerating into pointers (losing size information), and accidentally crossing enum types across libraries were all huge sources of bugs. Java doesn't have much of an excuse for its treatment of nullability, other than they were trying to court C/C++ developers.
I don't have 100% control of my coworkers or library writers. Knowing whether a library or a coworker can hand me back an optional value seems like a pretty basic thing for a language to tell you
Null Safety is the killer feature of modern programming languages like Swift and Kotlin. It provides a clarity you just can't get otherwise and saves you from runtime errors. And it requires very little additional ceremony to use.