Explicit nulls are in!
This has been a long time coming — the PR has been open for months, and folks have been asking for it for many years. But it’s finally real: in the Dotty master branch, there is now a concept of “explicit nulls”.
The docs can be found here; they will presumably be published in the next release. I recommend giving them a read, but here’s a tl;dr of the high points (and a few opinions).
There is a new compiler flag, named -Yexplicit-nulls
. Turning that on changes the type system in a critical way: null
is no longer an automatic bottom type of AnyRef
. That is, in this new compiler-switched world, reference types are not automatically nullable! (Yes, it’s a fix, or at least a workaround, for the billion-dollar mistake.)
You can still work with nulls, mind — that’s essential for interoperability — but when you turn on this compiler flag you now have to make your nulls explicit, using Scala 3’s new type unions, like this:
val maybeName: String | Null = ...
Yes, it’s wordier, but it’s much more Scala-ish. Nulls have always been a wart in Scala: an axiom of idiomatic Scala style is to use Option
instead of null
. But there’s never been a way to enforce that.
There’s more to the proposal, to make it easier to work in this environment. In particular:
- There is a “magic” subtype of
Null
calledJavaNull
. (Note: there is a current discussion of whether to make this name less Java-specific, since it also applies to, eg, JavaScript. Don’t be surprised if the name changes.) - Calls to Java automatically add
| JavaNull
to the result type, since it is idiomatic in Java to returnnull
pretty casually. JavaNull
calls can be chained without extra ceremony, like this:
val s2: String = someJavaMethod().trim().substring(2).toLowerCase()
Each of these steps can return null
, but the compiler allows them to chain together, and it is on your head if one of them throws a NullPointerException. (Personally, this is the one aspect of the proposal I don’t love — it’s convenient, but unprincipled enough that I would probably forbid its use in my codebases and would want a Scalafix rule to outlaw it. I’d rather see a more monadic approach, that passes the | Null
down the call chain, even if that requires the added ceremony of a for
comprehension. Hopefully that can be added at the library level, but I haven’t thought it through yet.)
- There is a new, rather magical concept of “flow typing”, that pays attention to the implications of an
if
statement and infers the nullability implications in its clauses, like this:
val s: String|Null = ???
if (s != null) {
// s: String
}
// s: String|Null
That bit is really, really cool, and hugely useful, although it feels a bit dangerous pedagogically: this is one of the most common mistakes that junior programmers make, assuming that things like this would work. Having it work in this one specific case, but nowhere else, seems likely to become a puzzler and a FAQ.
OTOH, if this is the camel’s nose in the tent to maybe expand flow-typing further in the future, and make it more principled and general, I’m willing to go with it, in the hopes of it eventually becoming a common and valuable tool.
Anyway, see the docs for the full details, but this enhancement is, IMO, a gigantic win, one of the more important benefits of Dotty. I expect that, once I convert my codebases to Scala 3, I would turn this flag on everywhere. I can understand why it’s not on by default for now — it would probably require a lot of code-rewriting in interoperability-heavy applications — but I think it makes working with Java/JavaScript more Scala-idiomatic and principled, and will likely lead to more-reliable code. Congrats to the Dotty team for getting it in before feature freeze!