Non-Nullability By Default

Just tried out Dotty 0.2.0-RC1 today and I found it suprising that the following code compiles:

  def take(b: Boo | Null): Boo = {
    val z: Boo = b
    z
  }

I can find some tidbits of discussion on https://gitter.im/lampepfl/dotty, but there doesn’t seem to be an issue on github tracking discussion on this topic.

Everything being nullable has always been one of those “yeah but we don’t do that” moments in using and teaching Scala, and I’d love to not do that in Scala 3. I personally think non-nullability by default is the sane choice for two main reasons:

  1. Type safety: I can use val b: Foo | Null when I don’t want to pay the cost of Option, without worrying about NPE
  2. Better interop with Java at the API level (You don’t need to use a wrapper function to wrap a nullable return value in Option)

What do you think?

6 Likes

For the time being, every class type is still nullable, so Boo | Null is the same as Boo. We have plans to change that, but we are not quite there yet. We want to concentrate on stability and easy migration first.

8 Likes

Good to hear! I was getting worried since I couldn’t find any definite source on this happening :slight_smile: Is there a github issue tracking this that I can follow?

Or we could add a new &~ and then we could exclude the Null type? eg String &~ Null

What would &~ mean in the context of any other type?

&~ is like a set difference for union types. However, I think that our final goal should be not-null by default. Leaving the type system "Null is a subtype of all reference types" might be continuing so-called the billion-dollar mistake.
I don’t think we need syntactic sugars for null manipulation like Kotlin since Scala has the concrete Option type.

I don’t think we need syntactic sugars for null manipulation like Kotlin since Scala has the concrete Option type.

Option currently has (almost) nothing to do with nullability. It is a collection of size 0 or 1. It can contain null.

Option.apply does coerce null to None but this is IMO actually inconsistent with the rest of the standard library.

For example, what do you think List(null, "a", "b").headOption returns? If option was a container that could not contain null, it would not be a functor, and could not be used in the standard lib for things like headOption – which returns None iff the list is empty, and it would be completely confusing and problematic if it also returned None if the first item in the list was null.

Option as a type has nothing to do with null safety. Only Option.apply + best practices lets it pretend to be.
In order for Scala the language to help, the type system has to change so that one can represent a non-nullable reference type and the compiler can track null introduction and elimination.

I don’t think you are contradicting each other. Once the type system can track nullability, you can handle null explicitly (e.g. by wrapping it in Option) in the (hopefully few) cases where it is necessary and use Option otherwise. No extra magic null handling syntax necessary.

Yes, if you tracked null introduction and elimination in the types, then Option.apply is one that eliminates null.

A non-null reference type B, and some place where it could be null…

val bOrNull: B | Null = ...

val b: B = Option(bOrNull)

would compile, because the compiler could track the null elimination and know that the signature of Option.apply removes any nullness of its parameter:

Option.apply(A a): A & NonNull

Clearly, we need some way to denote nullable and non-nullable reference types even to write clear method signatures. To write this example I needed both.

I’ve always thought Option.apply should be renamed to Option.fromNullable. If we had non-nullability by default, its signature would be fromNullabel(x: A | Null): Option[A], after all.

9 Likes

CN8q0MYVAAAIGDE

3 Likes

Solution: bats should be banned!

1 Like

I think it’d be nice to, at some point when Scala types are not nullable by default, to have the compiler understand that all Java types are. So if you have a function in java

public static String russianRoulette(){
return (new Random().nextInt(6) == 0 ? null : “safe”);
}

And then if you tried to use it from Scala, the compiler would know that the return type was actually String | Null and not String which would force the caller to handle that case.

What I kind of want is a Java concept of facades, the way we do with JavaScript, where you can concisely and clearly state whether this is a null-returning entry point or not…

1 Like

could we get away with the scala compiler treating any method without a scalasignature as returning A | Null unless the method has a @NonNull attribute?

I’m not a big java guy, but it looks like that was standardized:
https://jcp.org/en/jsr/detail?id=305

It would be ideal if we could just reuse that and push library authors to add that annotation vs make facades we need to maintain.

6 Likes

could we get away with the scala compiler treating any method without a scalasignature as returning A | Null unless the method has a @NonNull attribute?

That’s what we want to try, at least.

7 Likes

I really like the null handling choices made in Kotlin. We should be inspired from that.

1 Like