Transitioning to Explicit Nulls in Scala
Loading...
Date
Authors
Journal Title
Journal ISSN
Volume Title
Publisher
University of Waterloo
Abstract
Scala, being a language that compiles to the JVM, suffers from the problem that JVM bytecode references are implicitly nullable. Every dereference is therefore a potential null pointer exception. Scala's explicit nulls system removes this hazard at the type level: the null type is no longer a subtype of the reference types, and a nullable value must be declared as a union of its base type with null. The change is sound, but it is also global, and it invalidates a large body of existing Scala code as well as every assumption that Scala programs make about the nullability of the Java libraries they call. A null-safe type system that nobody migrates to is of little use, so the practical question is not whether explicit nulls is correct, but whether the ecosystem can be moved onto it.
This thesis describes and evaluates the four compatibility features that the Scala compiler provides to answer that question. The non-null cast lets a programmer assert non-nullness at a use site, using an intersection with a singleton type to preserve path-dependent typing. Flow typing tracks, for each block of code, a pair of asserted and retracted variable sets composed by sequencing and alternation operators, with a distinguished value for blocks that always terminate abruptly; this admits the common idiom of checking a variable against null before dereferencing it. Flexible types give a value obtained from Java a deliberately unsound pair of bounds that allows it to be used as either nullable or non-nullable, resolving an interoperation problem that neither a fully sound nor a fully permissive translation of Java types can solve for invariant type constructors such as arrays. Finally, the unsafe nulls language import relaxes null-related type checking within a lexical scope, and its counterpart safe nulls restores it, allowing null safety to be adopted incrementally at the granularity of a project, a file, or a single block.
We evaluate these features on two sets of real Scala projects. On the Community Build of 40 widely used libraries, 15 compile under explicit nulls with no changes at all; among the rest, most of the porting effort consists of widening declared types to nullable unions (45% of changed lines) and inserting non-null assertions (23%). An ablation study shows that flow typing and flexible types each carry a substantial and non-overlapping share of the migration burden: disabling flow typing surfaces 424 additional compilation errors across 17 projects, while disabling flexible types surfaces 1,579 across 31 projects. The two features are orthogonal, flow typing addressing null checks within Scala code and flexible types addressing values obtained from Java, and neither substitutes for the other. On the Open Community Build of 1,928 projects, enabling explicit nulls alone causes 854 projects to fail to compile; enabling it together with unsafe nulls reduces this to 16, a compatibility rate of 99.17%, with the remaining failures confined to two narrow and well-understood causes. These results indicate that explicit nulls, taken together with its compatibility features, is mature enough to be enabled by default in a future major release of the Scala compiler.