Problems with variance only occur once the compile-time and run-time type of an object differ:
val a: List[Any] = List("foo"): List[String]
Here a
has a statically weaker type (List[Any]
) than defined (List[String]
). Further, the same applies to a containing object (i.e. an element of the list). (Static type Any
, defined type String
).
If we have object-private (or object-protected) fields, this situation cannot occur:
trait A[-T] {
protected[this] val x: T
}
When we access x
we can be sure it is actually of type T
, even if A
is contra-variant, since the this
reference cannot be up-cast somewhere. (We always fully know our self-type).
So when we get back to Odersky's words here, let's have a look at:
val cont: A[String] = new A[Any] { ... }
cont.x // BAD! Is `Any`, not `String`
cont
is a reference to an object that has statically weaker type than defined with, that's why the reference to x
is not allowed. This up-cast situation will not happen with the this pointer, hence the following is OK:
trait B[-T] extends A[T] {
def foo() {
// here, this has always the most specific type.
val tmp: T = this.x
// do stuff
}
}