Swift Type Erasure

Jul 23, 2017

Type Erasure is a term that I’ve run across a few times as I’ve been developing in Swift, but have never really understood (until recently) what it is or when it can be useful. Hopefully I can change that by writing about it here!

The Motivation

Let’s start with a hypothetical situation where we have a ForceWielder protocol and various implementers of that protocol.

Now we want to take a sequence of force wielders and perform their attacks, so attempt to create the sequence:

letforceWielders:[ForceWielder]=[Anakin(),ObiWan()]// error: protocol 'ForceWielder' can only be used as a generic constraint// because it has Self or associated type requirements

Uh oh, seems like the ForceWielder protocol is not usable in this situation because it has an associated type. Ok, but why? The compiler is confused because there is type ambiguity in two different ways: the protocol itself can be implemented by a number of concrete types, and the associated type can also be defined as different types.

We’ll try using the Type Erasure pattern to make it so that we have a new type that is generic over the Ability associated type, but is a concrete implementer of the ForceWielder protocol. Something like this:

The Pattern

So we have a class that we can initialize by sending in a concrete ForceWielder, but we need to figure out how to forward the attack function on to the concrete instance that comes in. For this, we’ll create a container class that’s generic over ForceWielder and inherits from a base class that’s generic over Ability. The inheritance is what “erases” the ForceWielder type so that the super class only knows about Ability. Our wrapper will now look like this:

We are using the concrete class to instantiate an _AnyForceWielderBox (which knows about ForceWielder) but we are storing it as its superclass that only knows about Ability. Then we call attack on that stored _AnyForceWielderBase<Ability>. Let’s look at the code for these new classes…

privateclass_AnyForceWielderBase<Ability>:ForceWielder{init(){guardtype(of:self)!=_AnyForceWielderBase.selfelse{fatalError("Must be instantiated from a subclass")}}funcattack()->Ability{fatalError("Method must be overriden")}}privatefinalclass_AnyForceWielderBox<Concrete:ForceWielder>:_AnyForceWielderBase<Concrete.Ability>{privatevarconcrete:Concreteinit(_concrete:Concrete){self.concrete=concrete}overridefuncattack()->Concrete.Ability{returnconcrete.attack()}}

Using the New Type

Now we can create that sequence we wanted before…

letduelists=[AnyForceWielder(Anakin()),AnyForceWielder(ObiWan())]

Note that the type of duelists is inferred to be [AnyForceWielder<LightsaberCombat>].

duelists.map(){$0.attack()}// Attacking with Djem So form// Attacking with Soresu form

Also note that we are still getting type safety. If we were to try to combine Jedi and Sith…

letboth=[AnyForceWielder(Anakin()),AnyForceWielder(Emperor())]// error: heterogeneous collection literal could only be inferred to '[Any]';// add explicit type annotation if this is intentional

There has Got to be a Simpler Way

Ok, you got me. The pattern above was hard to understand but is pretty robust. Apple even uses it in the standard library. But if you just have a method or two that you need exposed (as in our example), you can just save it off instead of worrying about the two extra classes.