When is Nothing a legal receiver?

81 views Asked by At

Being the subtype of every other type allows a hypothetical Nothing typed value to be passed to any function. However, although such a value can serve as receiver for toString() it can't for unary_! (among others).

object Foo {
    def dead(q: Nothing): Unit = {
        println(q);
        q.toString();
        ((b: Boolean) => !b)(q);
        !q; // value unary_! is not a member of Nothing
    }
}

Is this a bug or a feature?

Note:

  1. This is the Scala version of an equivalent question I asked on Kotlin.
  2. Upcasting works: !(q.asInstanceOf[Boolean])
2

There are 2 answers

2
Tim On

Nothing is a subtype of every other type (including scala.Null); there exist no instances of this type

In other words, there are no values of type Nothing. So contrary to the statement in your question, you can't pass a Nothing typed value to any function (even hypothetically) because it doesn't exist, by definition. Neither can it be a receiver for any method because, again, it doesn't exist.

So the bug, if there is one, is that the compiler does not warn you that you have created a function that can never be called.


In this case, println(q) works because Nothing is a subtype of Any, and q.toString works because of an implicit conversion of AnyRef to Object which supports toString. The inline function converts q to Boolean which is also OK, but Object does not support unary_! so !q fails to compile.

2
Andrey Tyukin On

You don't need upcasting. You only have to ascribe some type which has a method unary_!:

def dead(q: Nothing): Unit = {
  !(q: Boolean)
}

Without an explicit type ascription, the method unary_! simply cannot be resolved, because even though Nothing is a subtype of Boolean, it's not a subclass of Boolean, therefore the compiler can not find a method unary_! in the inheritance hierarchy of Nothing.

The fact that you can define such methods and functions is not a bug either. The following is a completely valid program that uses a function with input type Nothing to produce a perfectly meaningful result 0, without throwing any errors or anything like it:

def foo[X](xs: List[X], f: (Int, X) => Int) = {
  xs.foldLeft(0)(f)
}

foo(Nil, (i: Int, n: Nothing) => 42)

The presence of Nothing in the type system is a Really Good Idea, because it's an initial object (for each other type A, there is exactly one function Nothing => A), and it simplifies lot of things, because it does not force you to deal with all kind of strange corner cases.