Hi Scala Community!
This thread is the SIP Committee’s request for comments on a proposal to make reference types non-nullable by default (e.g., val s: String = null would not be valid anymore). Nullable types would be expressed very naturally with a union type (e.g., val s: String | Null = null). You can find all the details here.
In the current version of Dotty, explicit nulls are enabled only with the flag -Yexplicit-nulls.
Motivation
The motivation is to considerably reduce the cases in which NullPointerExceptions can happen in a Scala codebase. It also helps the writing of performance-sensitive code that uses nulls on purpose, by providing help from the type checker to deal with those nulls.
Summary
Currently, in Scala, all reference types (types of a class or interface that extend AnyRef) admit two kinds of values:
- instances of the class or interface, which is good, and
-
null, which is bad.
When trying to call any method on null, a run-time NullPointerException is thrown. For example, the following program typechecks, but fails at run-time:
val x: String = null
println(x.substring(1))
With explicit nulls, a value of a reference type (such as String) does not admit null as a valid value anymore:
val x: String = null // compile error: String expected but Null found
We can use a union type to explicitly mark a nullable type. In that case, trying to call a method on a nullable type will result in a type error:
val x: String | Null = null // ok
println(x.substring(1)) // compile error: substring is not a member of String | Null
The type checker forces us to deal with the null case, for example using pattern matching:
x match {
case x: String => println(x.substring(1))
case null => println("it was null")
}
Flow typing
Because pattern matches can be burdensome for manipulating nullable types, we introduce a limited form a flow typing that is able to track the effective nullability of vals:
if (x != null) {
// x: String in this branch
println(x.substring(1)) // ok
} else
println("it was null")
Java interop
Since Java libraries have nullable reference types, we must interpret any reference type in a Java library as nullable. However, doing so naively by adding | Null everywhere forces way too many spurious tests for nulls when using APIs that do not in fact manipulate nulls. This can make the code significantly less readable.
The solution is to use a magical UncheckedNull instead when translating types from Java. A String | UncheckedNull still cannot be assigned to a String, but it is possible to call methods of String on it:
val javaStr = "hello".substring(3)
val s: String = javaStr // compile error: String expected but String | UncheckedNull found
println(javaStr.length) // ok, prints 2
This choice is obviously a design trade-off between usability and soundness.
More details
There are many more details in the documentation page:
https://dotty.epfl.ch/docs/reference/other-new-features/explicit-nulls.html
Make sure to read the details before commenting here.
Discussion
Opening this Proposal for discussion by the community to get a wider perspective.
– IMO, this is one of the lowest-hanging-fruit in Dotty. It helps with a pain point that almost any Scala project encounters if it works with Java libraries.