Laffen Laffen - 6 months ago 9
Swift Question

Why isn't [SomeStruct] convertible to [Any]?

Consider the following:

struct SomeStruct {}

var foo: Any!
let bar: SomeStruct = SomeStruct()

foo = bar // Compiles as expected

var fooArray: [Any] = []
let barArray: [SomeStruct] = []

fooArray = barArray // Does not compile; Cannot assign value of type '[SomeStruct]' to type '[Any]'


I've been trying to find the logic behind this, but with no luck.
It's worth mentioning if you change the struct to a class, it works perfectly.

One could always add a workaround and map each object of the fooArray and cast them to type of Any, but that is not the issue here. I'm looking for an explanation on why this is behaving like it is.

Can someone please explain this?

This SO question led me to this problem.

Answer

This seems to be a consequence of the fact that generics in Swift are invariant – not covariant. Remembering that [Type] is just syntactic sugar for Array<Type>, you can abstract away the arrays and Any protocol to hopefully see the problem better.

protocol Foo {}
struct Bar : Foo {}

struct Baz<T> {}

var f = Baz<Foo>()
var b = Baz<Bar>()

f = b // error: cannot assign value of type 'Baz<Bar>' to type 'Baz<Foo>'

Similarly with classes:

class Foo {}
class Bar : Foo {}

class Baz<T> {}

var f = Baz<Foo>()
var b = Baz<Bar>()

f = b // error: cannot assign value of type 'Baz<Bar>' to type 'Baz<Foo>'

This kind of covariant behaviour (upcasting) simply isn't possible with generics in Swift. In your example, Array<SomeStruct> is seen as a completely unrelated type to Array<Any> due to the invariance.

However, arrays seem to have a slight exception to this rule – they can silently deal with conversions from subclass types to superclass types under the hood. However, they don't appear to be able to do the same for structs.

To deal with this, you can simply perform your own conversion with the map function – allowing each element to get converted individually (as individual elements are covariant):

var fooArray: [Any] = []
let barArray: [SomeStruct] = []

// the 'as Any' isn't technically necessary as Swift can infer it,
// but it shows what's happening here
fooArray = barArray.map{$0 as Any} 

Although I see no reason why arrays shouldn't also be able to deal with such conversions under the hood as well as the class type conversions they can already do – I suspect it'll be possible in a future version of Swift.

But bear in mind however that Swift hates using non-concrete types for most non-trivial operations – see this Q&A for a great example.

For further reading about type variance in Swift, see this fantastic blog post on the subject.

Comments