[WIP] Scala with Explicit Nulls

Thank you for the detailed comments and suggestions.

Many of our decisions were guided by the point made several months ago that smooth migration of existing code is extremely important. On some decisions, perhaps we have prioritized migration a bit too much.

On equality, in existing Scala, null supports eq/ne because Null <: AnyRef, but our proposal makes this no longer true. Thus we wanted to support eq/ne for backwards compatibility. But since, as you point out, testing for null can still be done with ==/!=, I agree that we can remove eq/ne from Null and remove the RefEq trait that we introduced. I don’t feel strongly either way about a warning for ==/!=; we can add one if desired.

I think we agree with your suggestion to put the implicits behind an import. Our point was merely that we do want to have these implicits available somewhere, but they do not have to be globally in scope everywhere.

On nf(A | B) and nf(A & B), I will let @abeln comment, but I think these are an artifact of an idea that we eventually rejected, which was to treat .class files compiled with existing versions of Scala (without the explicit null support) the same way as .class files coming from Java. That is, we would have applied nf() to all types in such .class files. This would have been more sound but would have made migration more difficult, and especially would make it difficult to maintain codebases that need to be compiled with different Scala versions. We decided instead to keep the types in existing Scala .class files as they are, so for example, if a method has a return type String, we take it on faith that it will not return null. This is unsound for now but makes migration easier, and we will have soundness eventually once all code (and all libraries…) have migrated.

Not being able to write JavaNull explicitly came out of an earlier discussion with @odersky. JavaNull is a bit of an unpredictable hack, and we wanted to discourage its spread throughout a codebase away from the Java interop boundary. That said, since it is just an alias for an annotated Null which itself can be written, I don’t feel strongly about preventing JavaNull from being written. We can discourage it in documentation rather than banning it outright.

Flow-sensitive inference is the one area where I think I disagree with you. Yes, technically it is an orthogonal issue, and yes, it could be generalized to support things other than null. I also share your nervousness about there being no precedent for any similar flow-sensitive inference in current Scala. However, looking at existing code, we concluded that the if(x != null) idiom is so widespread that we cannot have a smooth migration without some support for flow-sensitive inference. We originally wanted to delay implementing it, but found it necessary even just to get many of the tests in the Dotty test suite to pass: many of them use x as non-null after a null check. Porting the standard library and getting Dotty to bootstrap will also require this: both contain many instances of if(x != null). Yes, I agree that it would be more consistent with the rest of the language if people just wrote something like:

x match {
  case null => ...
  case nn: T => ...
}

instead of if(x == null) ... else .... But the point is that existing code uses if for this in many places, not match.

A more general version of flow-sensitive typing will take a long time to reach consensus on, and might never be accepted to the language. We don’t want to block explicit nulls waiting for a general flow-sensitive typing feature that may never come.

That said, I do also feel uneasy about opening this can of worms without precedent, and thus I would welcome any alternative suggestions that we may not have thought of to ease migration of existing uses of if(x != null).

5 Likes