Why Can't Swift Automatically Convert a Generic Type Parameter to Its Superclass

Why can't Swift automatically convert a generic type parameter to its superclass?

Swift generics are invariant, meaning that two generic types are unrelated even if their generic type parameters have an inheritance relation. Invariant generic types cannot be cast to each other since they have no inheritance relation to each other.

This means that GenericStruct<B> and GenericStruct<A> are completely unrelated even if B is a subclass of A, hence you cannot cast one to the other.

How do I store a value of type Class ClassImplementingProtocol in a Dictionary of type [String:Class Protocol ] in Swift?

A Thing<Vanilla> is not a Thing<Flavor>. Thing is not covariant. There is no way in Swift to express that Thing is covariant. There are good reasons for this. If what you were asking for were allowed without careful rules around it, I would be allowed to write the following code:

func addElement(array: inout [Any], object: Any) {
array.append(object)
}

var intArray: [Int] = [1]
addElement(array: &intArray, object: "Stuff")

Int is a subtype of Any, so if [Int] were a subtype of [Any], I could use this function to append strings to an int array. That breaks the type system. Don't do that.

Depending on your exact situation, there are two solutions. If it is a value type, then repackage it:

let thing = Thing<Vanilla>(value: Vanilla())
dict["foo"] = Thing(value: thing.value)

If it is a reference type, box it with a type eraser. For example:

// struct unless you have to make this a class to fit into the system, 
// but then it may be a bit more complicated
struct AnyThing {
let _value: () -> Flavor
var value: Flavor { return _value() }
init<T: Flavor>(thing: Thing<T>) {
_value = { return thing.value }
}
}

var dict = [String:AnyThing]()
dict["foo"] = AnyThing(thing: Thing<Vanilla>(value: Vanilla()))

The specifics of the type eraser may be different depending on your underlying type.


BTW: The diagnostics around this have gotten pretty good. If you try to call my addElement above in Xcode 9, you get this:

Cannot pass immutable value as inout argument: implicit conversion from '[Int]' to '[Any]' requires a temporary

What this is telling you is that Swift is willing to pass [Int] where you ask for [Any] as a special-case for Arrays (though this special treatment isn't extended to other generic types). But it will only allow it by making a temporary (immutable) copy of the array. (This is another example where it can be hard to reason about Swift performance. In situations that look like "casting" in other languages, Swift might make a copy. Or it might not. It's hard to be certain.)

How to use generic types to get object with same type

Update: For a better solution, see Rob's answer.


Similarly as in How can I create instances of managed object subclasses in a NSManagedObject Swift extension?,
this can be done with a generic helper method:

extension NSManagedObject {

func transferTo(context context: NSManagedObjectContext) -> Self {
return transferToHelper(context: context)
}

private func transferToHelper<T>(context context: NSManagedObjectContext) -> T {
return context.objectWithID(objectID) as! T
}
}

Note that I have changed the return type to Self.
objectWithID() does not return an optional
(in contrast to objectRegisteredForID(), so there is no need to
return an optional here.

Update: Jean-Philippe Pellet's suggested
to define a global reusable function instead of the helper method
to cast the return value to the appropriate type.

I would suggest to define two (overloaded) versions, to make this
work with both optional and non-optional objects (without an unwanted
automatic wrapping into an optional):

func objcast<T>(obj: AnyObject) -> T {
return obj as! T
}

func objcast<T>(obj: AnyObject?) -> T? {
return obj as! T?
}

extension NSManagedObject {

func transferTo(context context: NSManagedObjectContext) -> Self {
let result = context.objectWithID(objectID) // NSManagedObject
return objcast(result) // Self
}

func transferUsingRegisteredID(context context: NSManagedObjectContext) -> Self? {
let result = context.objectRegisteredForID(objectID) // NSManagedObject?
return objcast(result) // Self?
}
}

(I have updated the code for Swift 2/Xcode 7. The code for earlier
Swift versions can be found in the edit history.)

Swift generic coercion misunderstanding

The difference is that Array (and Set and Dictionary) get special treatment from the compiler, allowing for covariance (I go into this in slightly more detail in this Q&A).

However arbitrary generic types are invariant, meaning that X<T> is a completely unrelated type to X<U> if T != U – any other typing relation between T and U (such as subtyping) is irrelevant. Applied to your case, Signal<ChildClass> and Signal<BaseProtocol> are unrelated types, even though ChildClass is a subtype of BaseProtocol (see also this Q&A).

One reason for this is it would completely break generic reference types that define contravariant things (such as function parameters and property setters) with respect to T.

For example, if you had implemented Signal as:

class Signal<T> {

var t: T

init(t: T) {
self.t = t
}
}

If you were able to say:

let signalInt = Signal(t: 5)
let signalAny: Signal<Any> = signalInt

you could then say:

signalAny.t = "wassup" // assigning a String to a Signal<Int>'s `t` property.

which is completely wrong, as you cannot assign a String to an Int property.

The reason why this kind of thing is safe for Array is that it's a value type – thus when you do:

let intArray = [2, 3, 4]

var anyArray : [Any] = intArray
anyArray.append("wassup")

there are no problems, as anyArray is a copy of intArray – thus the contravariance of append(_:) is not a problem.

However, this cannot be applied to arbitrary generic value types, as value types can contain any number of generic reference types, which leads us back down the dangerous road of allowing an illegal operation for generic reference types that define contravariant things.


As Rob says in his answer, the solution for reference types, if you need to maintain a reference to the same underlying instance, is to use a type-eraser.

If we consider the example:

protocol BaseProtocol {}
class ChildClass: BaseProtocol {}
class AnotherChild : BaseProtocol {}

class Signal<T> {
var t: T

init(t: T) {
self.t = t
}
}

let childSignal = Signal(t: ChildClass())
let anotherSignal = Signal(t: AnotherChild())

A type-eraser that wraps any Signal<T> instance where T conforms to BaseProtocol could look like this:

struct AnyBaseProtocolSignal {
private let _t: () -> BaseProtocol

var t: BaseProtocol { return _t() }

init<T : BaseProtocol>(_ base: Signal<T>) {
_t = { base.t }
}
}

// ...

let signals = [AnyBaseProtocolSignal(childSignal), AnyBaseProtocolSignal(anotherSignal)]

This now lets us talk in terms of heterogenous types of Signal where the T is some type that conforms to BaseProtocol.

However one problem with this wrapper is that we're restricted to talking in terms of BaseProtocol. What if we had AnotherProtocol and wanted a type-eraser for Signal instances where T conforms to AnotherProtocol?

One solution to this is to pass a transform function to the type-eraser, allowing us to perform an arbitrary upcast.

struct AnySignal<T> {
private let _t: () -> T

var t: T { return _t() }

init<U>(_ base: Signal<U>, transform: @escaping (U) -> T) {
_t = { transform(base.t) }
}
}

Now we can talk in terms of heterogenous types of Signal where T is some type that's convertible to some U, which is specified at the creation of the type-eraser.

let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal, transform: { $0 }),
AnySignal(anotherSignal, transform: { $0 })
// or AnySignal(childSignal, transform: { $0 as BaseProtocol })
// to be explicit.
]

However, the passing of the same transform function to each initialiser is a little unwieldy.

In Swift 3.1 (available with Xcode 8.3 beta), you can lift this burden from the caller by defining your own initialiser specifically for BaseProtocol in an extension:

extension AnySignal where T == BaseProtocol {

init<U : BaseProtocol>(_ base: Signal<U>) {
self.init(base, transform: { $0 })
}
}

(and repeat for any other protocol types you want to convert to)

Now you can just say:

let signals: [AnySignal<BaseProtocol>] = [
AnySignal(childSignal),
AnySignal(anotherSignal)
]

(You can actually remove the explicit type annotation for the array here, and the compiler will infer it to be [AnySignal<BaseProtocol>] – but if you're going to allow for more convenience initialisers, I would keep it explicit)


The solution for value types, or reference types where you want to specifically create a new instance, to is perform a conversion from Signal<T> (where T conforms to BaseProtocol) to Signal<BaseProtocol>.

In Swift 3.1, you can do this by defining a (convenience) initialiser in an extension for Signal types where T == BaseProtocol:

extension Signal where T == BaseProtocol {
convenience init<T : BaseProtocol>(other: Signal<T>) {
self.init(t: other.t)
}
}

// ...

let signals: [Signal<BaseProtocol>] = [
Signal(other: childSignal),
Signal(other: anotherSignal)
]

Pre Swift 3.1, this can be achieved with an instance method:

extension Signal where T : BaseProtocol {
func asBaseProtocol() -> Signal<BaseProtocol> {
return Signal<BaseProtocol>(t: t)
}
}

// ...

let signals: [Signal<BaseProtocol>] = [
childSignal.asBaseProtocol(),
anotherSignal.asBaseProtocol()
]

The procedure in both cases would be similar for a struct.

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

Swift 3 Update

As of Swift 3 (specifically the build that ships with Xcode 8 beta 6), collection types can now perform under the hood conversions from value-typed element collections to abstract-typed element collections.

This means that the following will now compile:

protocol SomeProtocol {}
struct Foo : SomeProtocol {}

let arrayOfFoo : [Foo] = []

let arrayOfSomeProtocol : [SomeProtocol] = arrayOfFoo
let arrayOfAny : [Any] = arrayOfFoo

Pre Swift 3

This all starts with 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 to hopefully see the problem better.

protocol Foo {}
struct Bar : Foo {}

struct Container<T> {}

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

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

Similarly with classes:

class Foo {}
class Bar : Foo {}

class Container<T> {}

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

f = b // error: cannot assign value of type 'Container<Bar>' to type 'Container<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 have an exception to this rule – they can silently deal with conversions from subclass types to superclass types under the hood. However, they don't do the same when converting an array with value-typed elements to an array with abstract-typed elements (such as [Any]).

To deal with this, you have to perform your own element-by-element conversion (as individual elements are covariant). A common way of achieving this is through using map(_:):

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}

A good reason to prevent an implicit 'under the hood' conversion here is due to the way in which Swift stores abstract types in memory. An 'Existential Container' is used in order to store values of an arbitrary size in a fixed block of memory – meaning that expensive heap allocation can occur for values that cannot fit within this container (allowing just a reference to the memory to be stored in this container instead).

Therefore because of this significant change in how the array is now stored in memory, it's quite reasonable to disallow an implicit conversion. This makes it explicit to the programmer that they're having to cast each element of the array – causing this (potentially expensive) change in memory structure.

For more technical details about how Swift works with abstract types, see this fantastic WWDC talk on the subject. For further reading about type variance in Swift, see this great blog post on the subject.

Finally, make sure to see @dfri's comments below about the other situation where arrays can implicitly convert element types – namely when the elements are bridgeable to Objective-C, they can be done so implicitly by the array.

Array of custom class implementing generics not allowing custom class with subclassed generic

Actually you're not putting the case strongly enough. Define:

class Human {}
class Child: Human {}
struct Holder<T> {}

I made Holder a struct so no one can accuse us of cheating: Array is a struct, Holder is a struct. And I took away your constraint on the placeholder, reducing everything to the simplest possible form.

Now just assign an array of Child where an array of Human is expected:

var arr = Array<Human>()
arr = Array<Child>()

Fine. Now try it with a Holder:

var holder = Holder<Human>()
holder = Holder<Child>() // error

The parallelism now appears perfect: Array is a struct, Holder is a struct, and all we're doing is trying to assign polymorphically. So what's the problem?

The problem, as you have probably already suspected, is that you are not Apple. Apple writes the code, so they get to define Array and similar types as covariant on the parametrized type. But it isn't an automatic feature of the language — that is, it is not true for generics in general. And in particular, you don't get to do that for types you define.

So Apple's Array is covariant, but your Holder (or Person) is not, and there's nothing that allows you to switch covariance on.

You can see why Array is covariant. It is a very special case. An array is a collection of objects. Apple knows that an array of, say, Child objects is also in fact an array of Human objects, because every Child is a Human (polymorphism). So they have implemented covariance for arrays, to ensure that this is so.

But there is no such guarantee about your Person or my Holder. Swift doesn't know what you intend to do with the placeholder T. You can probably think of cases where substituting a Holder<Child> where a Holder<Human> is expected would be wrong. So Apple makes no assumptions in that direction.


I should add that it's important to distinguish the following:

class Human {}
class Child: Human {}
struct Holder<T> {
let thing : T
}
let holder : Holder<Human> = Holder(thing:Child()) // fine

That's legal, but it has nothing whatever to do with what we've been talking about. Only one generic type is involved here: Holder<Human>. All we're doing is assigning a Child into thing, where a Human is expected. That's good old-fashioned non-generic polymorphism. But you still can't cast a Holder<Human> down to a Holder<Child>, even if thing is a Child, and you still can't assign a Holder<Child> where a Holder<Human> is expected.



Related Topics



Leave a reply



Submit