Scala Corner Case#2: "Nothing" Can not Be Cast and Assigned to a val/var
There is a Java class which takes type parameter T, T is any type:
package dcaoyuan.test; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class AList<T> implements Iterator<T> { List<T> a = new ArrayList<T>(); { a.add((T) "string"); } Iterator<T> itr = a.iterator(); public boolean hasNext() { return itr.hasNext(); } public T next() { return itr.next(); } public void remove() { throw new UnsupportedOperationException("Not supported yet."); } }
Which implemented java.util.Iterator with type parametered T next(). Notice here next() will return T
The above code simulates a case, that an existed Java lib may return an instance that has type parameter erased, on run-time.
Now let's try to call it in Scala:
package dcaoyuan.test object Main { def main(args: Array[String]) :Unit = { testAList_work testAList_unwork } def testAList_work :Unit = { val a :AList[_] = new AList // a is inferred as AList[Any] while (a.hasNext) { val e = a.next println(e) } } def testAList_unwork :Unit = { val a = new AList // a is inferred as AList[Nothing] while (a.hasNext) { // now a.next is Nothing, can not cast to "val e", which should at least be Any val e = a.next // will throw java.lang.ClassCastException println(e) } } }
I wrote two functions: "testAList_work" and "testAList_unwork"
The issue is in testAList_unwork, as a simple "val a = new AList", "a" will be inferred by scalac as AList[Nothing], so, "a.next" will return an instance of type Nothing, then, "val e = a.next" will throw java.lang.ClassCastException
My question is, should this corner case be checked by scalac as an error or warning? So I do not need to dig the clause when it happens on run-time.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/blog_logo.png)
rss
Comments
Actually, the real problem is in the definition of AList where you do an unsafe cast
If you didn't do that cast, you would never get a class cast exception later.
Inferring Nothing might seem strange, and perhaps there should be a warning since it's rarely useful, but it doesn't lead to class cast exceptions in general.
You would get a class cast exception if you wrote val a = new AList[Int]; val e = a.next, too. And you'd get the same class cast exception in Java.
Here's a simplified version to illustrate that "Nothing" isn't the problem, it's the cast.
scala> class Foo[T] { def next : T = "hello".asInstanceOf[T] } defined class Foo
scala> val a : Foo[_] = new Foo a: Foo[_] = Foo@135572b
scala> a.next res5: Any = hello
scala> new Foo res0: Foo[Nothing] = Foo@f6852d
scala> res0.next java.lang.ClassCastException?:
scala> new Foo[Int] res2: Foo[Int] = Foo@6e4ba6
scala> res2.next java.lang.ClassCastException?:
James,
Nice example code.
Yes. The real problem is in AList, which, in real java world, could not be avoidable sometimes. And, Imaging that Scala call an existed java jar lib, which may pass an instance just like AList, to Scala. If scalac infer it as List[Nothing], then a CCE is thrown on run-time. That's what I exactly encountered.
The same code won't throw CCE in Java, here is the code:
package dcaoyuan.test; public class AListTest {
}
The code in Java, force you to declare "e" as type "Object", so no CCE will be thrown. But in Scala, you can omit the type, just write "val e = a.next", so, let scalac to infer the type, the result is: it can pass scalac, but fail on run-time.
I think the key is scalac infer it as AList[Nothing] instead of AList[Any], maybe this is for scalac's insistence, but at least, I think here is a corner case.
"Nothing" and "Null" are special in Scala, which are not in the major type hierarchy of scala's "Any" based type hierarchy. I need to understand "Nothing" and "Null" better.
The reason it works in one language but not the other is that Java has raw types and Scala doesn't. Scala does have type inference though, and when the compiler has no clues on how to infer a type parameter it tends to infer "Nothing." This "corner case" you are seeing is just that "AList" without type parameters means something different in the two languages. In Java: treat as a raw type. In Scala: infer the type parameter.
Your AList code will cause CCE in Java if you create an AList<Integer>. It has an unsafe cast and runtime CCE's are the cost of unsafe casts.
Nothing and Null are subtypes of Any, so they do fit in the same type hierarchy. The difference is that unlike all the other types, they change the shape of the hierarchy from being a tree to being a DAG.
val a : String = null
but this is not because Int is not a reference type.
val a : Int = null
So Scala's "Null" is just a formalization of a rule that already exists in Java.
Nothing is the subtype of all types. It's "uninhabited" - there are no values of type Nothing. It represents the return type of a function that returns nothing - i.e. one that never returns normally. For example it could loop infinitely or always throw an exception. Because it is the subtype of all types, this is legal
def undefined : Nothing = error("not defined")
def s : String = undefined def i : Int = undefined
Because Nothing is the subtype of all types it's useful for certain sentinel values. For instance, Scala's Nil extends List[Nothing]. You can see why that makes sense because if you take the head of Nil you get nothing but an exception.
For more about Nothing see http://en.wikipedia.org/wiki/Bottom_type
This was my entry into the "void" of nothing: http://oldfashionedsoftware.com/2008/08/20/a-post-about-nothing/
@eagertolearn, in Figure 11.1 of Programming In Scala, "Nothing" is not in the same hierarchy of "Any". And the <a href=" http://www.scala-lang.org/docu/files/api/scala/Nothing.html">Nothing</a> in document says the same thing as you said: Nothing is subtype of Any.
I'm a bit confused :-)