Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type coercion issue in Swift

EDIT: This works absolutely fine in Swift 3, which we should all be using by now :)


If I have two protocols, X and Y where Y implements X, why can't I assign an array of Y to a variable with the type [X]?

Even more curiously, I can convert it one by one into an array of X, and that compiles fine.

protocol X { }

protocol Y: X { }

// Make a test array of Y
extension String: Y { }
let a: [Y] = [ "Hello" ]

// Make it into an array of X - this works absolutely fine
let b: [X] = [ a[0] as X ]

// Why won't this cast work?
let c: [X] = a as [X]

I thought that given Y does everything X can do, this assignment should be fine (yea, I lose some type information, but it should at least compile!)

Any ideas?

EDIT: The current answer points out that it's a dangerous thing to do if you are using mutable arrays - which I didn't get but do now :) However, if my arrays are all immutable why won't Swift let this happen? Even if c is a mutable array (i.e. var c = a as [X]) why won't Swift copy it, leaving a intact?

like image 729
deanWombourne Avatar asked Apr 12 '16 14:04

deanWombourne


1 Answers

This doesn't work because it could create a few problems. For example:

var squares: Array<Square> = Array<Square>()
(squares as [Shape]).append(Circle())

Now we have a circle in our Array of Squares. We don't want that, so the compiler doesn't let us. In other languages like Scala you can specify generics to be covariant, contravariant or invariant.

If Swift would let you use covariant generics an Array<Square> would be a subtype of Array<Shape>.

If using contravariance an Array<Square> would be a supertype(!) of Array<Shape>.

But when using invariant generics, neither is the subtype of neither.

The easiest way to do it in your case would probably be this:

let c = a.map{$0 as X}

Now c is of type [X].

For more information on type variance and why it can be problematic visit, you can visit this wiki page.

EDIT: After further back and forth, it seems the real problem is, that Protocols allow default implementations and can therefore cause problems. Your code will compile flawlessly when using classes. Here's some code that could potentially lead to problems with protocols:

protocol Shape {
    func surfaceArea() -> Double
}

extension Shape {
    func surfaceArea() -> Double {
        return 0.0
    }
}

protocol Circle : Shape {
    func getRadius() -> Double
}

extension Circle {
    func surfaceArea() -> Double {
        return getRadius() * getRadius() * 3.14
    }
}

Now, when we upcast between these two protocols, the surfaceArea() method returns different values, and Swift doesn't allow it.

Try the same thing with classes, instead of protocols and Swift won't have any problems compiling your current code. This is, to me, kind of a weird decision by the Swift team, but I still think the best way to mitigate is to just use

let c = a.map{$0 as X}
like image 99
Luka Jacobowitz Avatar answered Nov 14 '22 05:11

Luka Jacobowitz