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 NullPointerException
s can happen in a Scala codebase. It also helps the writing of performance-sensitive code that uses null
s on purpose, by providing help from the type checker to deal with those null
s.
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 val
s:
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 null
s when using APIs that do not in fact manipulate null
s. 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.