9

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?

15
  • Swift generics are invariant. Not sure you can have covariant generics in swift. Commented Apr 12, 2016 at 14:47
  • The problem with this code is, that you could add a Z to the hierarchy which also derives from X. Now your Array has both Y and Z, even though Z is not a subtype of Y. Commented Apr 12, 2016 at 14:56
  • @LukaJacobowitz: I don't think this is the problem. Infact you can still individually append different types that conform to Y. E.g. struct Z: X {};b.append(Z()). Now b does contain an element of type String and an element of type Z. And the compiler is fine with that. Commented Apr 12, 2016 at 15:06
  • 1
    I know, but consider this: ` (a as [X]).append(Z())` now you added a Z to an array of Y Commented Apr 12, 2016 at 15:41
  • 1
    By the way guys, this works fine in Swift 3 Commented Dec 13, 2016 at 16:20

1 Answer 1

7

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}
Sign up to request clarification or add additional context in comments.

10 Comments

Your first block of code does not work for a different reason. The compiler error is error: cannot use mutating member on immutable value of type '[Shape]'. This is NOT related to the original question. Would be the same with var list = [1,2,3];(list as [Int]).append(4) where we use the exact same type. Please read my comments below the question.
Nice explanation. In my case I know I'm safe with this cast, but I can see why it's not allowed so hey, I'll just find a different way to do what I'm trying to do :)
@appzYourLife My question has absolutely nothing to do with mutability and all to do with generics. So what if the code example doesn't compile, it's the concept that's important.
Exactly but the explanation provided here is not the answer to your question. This is not the reason why your last line of code does not compile.
@LukaJacobowitz: exactly! if a is an immutable array, why can't I make a new array c by losing the type information?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.