Hello everyone,
I stumbled upon a puzzling situation while fooling around with the type system. Let's say we define the following protocol to describe immutable stack-like types:
protocol Stack {
associatedtype Element
func push(element: Element) -> Self
}
Now let's imagine I want to conform to this protocol with a simple linked list implementation:
class List<Element>: Stack {
var element: Element
var next: List?
init(element: Element, next: List? = nil) {
self.element = element
self.next = next
}
func push(element: Element) -> List {
return List(element: element, next: self)
}
}
This code won't compile with a pretty clear error message. The problem is that Stack
requires the method push
to return Self
, but I'm returning List<Element>
in a non-final class. Therefore the method requirements won't be fulfilled in any subclass of List
.
The compiler offers to switch the return type of push
with Self
and then to force downcast the return expression to Self
, in order to respect the now modified return type. While I understand why this type checks, I don't understand why the program doesn't crash at runtime. More concretely, why does this code run fine:
class List<Element>: Stack {
// ...
func push(element: Element) -> Self {
// This line should fail at runtime if the receiver is a subclass of `List<T>`
return List(element: element, next: self) as! Self
}
}
class IntList: List<Int> {}
var i = IntList(element: 0)
i = i.push(element: 1) // This method call should crash
After scratching my head for a while, I modified IntList
to contain an additional stored property and printed it at the end of the program to confirm my suspicions.
class IntList: List<Int> {
let foo: String
// ...
}
var i = IntList(foo: "bar", element: 0)
i = i.push(element: 1)
print(i.foo) // This line does crash the program
While I perfectly understand why the last line crashes, my understanding is that the forced downcast should have already crashed earlier. Am I missing something?