I want to store a more specialized type in a Dictionary of type [String:SomeClass]. Here is some sample code illustrating my problem (also available to play with at https://swiftlang.ng.bluemix.net/#/repl/579756cf9966ba6275fc794a):
class Thing<T> {}
protocol Flavor {}
class Vanilla: Flavor {}
var dict = [String:Thing<Flavor>]()
dict["foo"] = Thing<Vanilla>()
It produces the error ERROR at line 9, col 28: cannot assign value of type 'Thing<Vanilla>' to type 'Thing<Any>?'
.
I've tried casting Thing<Vanilla>() as Thing<Flavor>
but that produces the error cannot convert value of type 'Thing<Vanilla>' to type 'Thing<Flavor>' in coercion
.
I've also tried to define the Dictionary as type [String:Thing<Any>]
but that doesn't change anything either.
How do I create a collection of different Thing
s without resorting to plain [String:AnyObject]
?
I should also mention that the class Thing
is not defined by me (in fact it's about BoltsSwift Task
s), so the solution to create a base class of Thing
without a type parameter doesn't work.
A
Thing<Vanilla>
is not aThing<Flavor>
.Thing
is not covariant. There is no way in Swift to express thatThing
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:Int
is a subtype ofAny
, 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:
If it is a reference type, box it with a type eraser. For example:
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: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.)One way to solve this is adding an initialiser to
Thing
and creating aThing<Flavor>
that will hold aVanilla
object.It will look something like: