Home > Software design >  Why does downcasting my class fail, but upcasting then downcasting succeed?
Why does downcasting my class fail, but upcasting then downcasting succeed?

Time:12-14

I have the following classes:

class Shape {
    var name: String
    var area: Int?
    init(name: String) {
        self.name = name
    }
}

class Rectangle : Shape {
    public func calculateArea(width: Int, height: Int) {
        self.area = width * height
    }
}

I get a Shape, which I decide to treat as a Rectangle. My understanding is that I should downcast, like so:

let someShape = Shape(name: "Test shape")
let rect = someShape as! Rectangle

This fails. From Xcode:

Could not cast value of type 'Shape' (0x103c76910) to 'Rectangle' (0x103c76530)

If I instead start with a Rectangle, upcast to a Shape, and downcast back to Rectangle, it works:

let rect = Rectangle(name: "Test rect")
let someShape = rect as! Shape
let downcastRect = someShape as! Rectangle

Why does my first downcast fail, but my second downcast succeed?

CodePudding user response:

The instance doesn't change when casting the type. The instance is the actual allocated object being held in memory. The variable is just a reference to the instance.

In the first example, the instance is a Shape. The variable that references the instance can't be of type Rectangle.

In the second example, the instance is a Rectangle. When it's cast to a Shape, the instance someShape references is still a Rectangle. That allows for someShape to be cast back to Rectangle.

CodePudding user response:

The reason is simple - upcasting and downcasting does not change the type of the instance, it only changes the type of the variable.

In the first case you have an instance of Shape, which you cannot cast to a Rectangle because not all shapes are rectangles.

In the second case you start with an instance of Rectangle which you store to a Shape variable. However, the instance is still a Rectangle. Then of course you can downcast it back to Rectangle because it's still a Rectange. If you tried to cast to a different shape, e.g. a Triangle, the cast would fail.

That's the whole reason why we have operators like is and as?. In the first case:

let someShape = Shape(name: "Test shape")
print(someShape is Rectangle) // false - this is not a Rectangle

and in the second case:

let rect = Rectangle(name: "Test rect")
let someShape = rect as! Shape
print(someShape is Rectange) // true - someShape is still a Rectangle

Also note that you have a warning for rect as! Shape. The compiler knows that every Rectangle is a Shape and you can cast safely using rect as Shape or directly write:

let someShape: Shape = rect
  • Related