Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift error with generic array

Tags:

generics

swift

I have a very simple playground:

protocol MyProtocol {}

struct MyType: MyProtocol {}

class MyClass <T: MyProtocol> {
    func myFunction(array: [T]) {
        if let myArray = array as? [MyType] {
            println("Double!")
        }
    }
}

let instance = MyClass<MyType>()

let array = [MyType(), MyType()]

instance.myFunction(array)

Then it says "MyType is not a subtype of 'T'" on the if let line. Well, I think, however, MyType and T are compatible.

When I modified the if let statement, it actually works:

if let first = array.first as? MyType

But now I can't cast array to [MyType] (Sure, I know it is Swift's static typing specification.)

I'm wondering what the problem is. My understanding about Generics? Or is it Swift language's limitation? If so, is there any way to do like this?

Thanks in advance.

like image 888
Akkyie Avatar asked Feb 28 '15 21:02

Akkyie


2 Answers

Swift doesn’t have builtin behaviour to speculatively convert an array’s contents from one arbitrary type to another. It will only do this for two types that it knows have a subtype/supertype relationship:

class A { }
class B: A { }
let a: [A] = [B(),B()]
// this is allowed - B is a subtype of A
let b = a as? [B]

let a: [AnyObject] = [1,2,3]
// this is allowed - NSNumber is a subtype of AnyObject
let b = a as? [NSNumber]

struct S1 { }
struct S2 { }

let a = [S1(),S1()]
// no dice - S2 is not a subtype of S1
let b = a as? [S2]

The protocol doesn’t help either:

protocol P { }
struct S1: P { }
struct S2: P { }

let a = [S1(),S1()]
// still no good – just because S1 and S2 both conform to P
// doesn’t mean S2 is a subtype of S1
let b = a as? [S2]

Your example is basically a variant of this last one. You have an array of type [T], and you want to cast it to [MyType]. It’s important to understand that you do not have an array of type [MyProtocol]. Your generic type T is a specific type, which has to implement MyProtocol, but that’s not the same thing.

To see why you can’t just cast from any type to any other type, try this code:

protocol P { }
struct S: P { }

let a: [P] = [S(),S()]
let b = a as? [S]

This will generate a runtime error: "fatal error: can't unsafeBitCast between types of different sizes". This gives a hint as to why you can only cast an array from containing one reference type to a subtype – it’s because what is happening is just a bit cast from one pointer type to another. This will work for super/subtype classes, but not for arbitrary classes, structs, or protocols, as they have different binary representations.

like image 56
Airspeed Velocity Avatar answered Nov 05 '22 16:11

Airspeed Velocity


Generic on a child-type is not a subtype of the same generic on a parent-type.

[MyProtocol] in Swift actually translates to Array<MyProtocol> (i.e. a generic). Same goes for [MyType] being a shortcut for Array< MyType >. This is why one does not directly cast to another.

like image 44
0x416e746f6e Avatar answered Nov 05 '22 17:11

0x416e746f6e