Type Erasures in swift

What is it and how does it solve the problem of equality check.

Saravanakumar S
3 min readFeb 20, 2021

The problem:

As engineers, we encounter quite number of design problems in our day to day life. One such problem is enabling your protocol to be Equatable. We eventually want our domain layer to be as generic as possible. This means, we want our domain layer to contain abstract representations of object rather than a concrete type. This poses problem in some scenarios.

Consider you are building a vehicle inventory app, one of the core domain object requirement would be Vehicle

protocol Vehicle {var model: String { get }var brand: String { get }}

And you would want your `Vehicle` to equatable so that you can ensure that you’re not making any duplicate entries in your inventory system.

protocol Vehicle: Equatable {...}

Suddenly, the swift compiler will start throwing error on usages of Vehicle saying,

Reasoning:

What does this error actually means? To understand it, lets look at the signature of == method of Equatable

static func == (lhs: Self, rhs: Self) -> Bool

The swift compiler simply can’t compare two objects for equivalence unless it knows the concrete types that conforms to the protocol. In real world, this protocol can be conformed by any number of struct/class/enums. So, it won’t make sense to compare them for equality just because all of the types conforms to the same protocol right?

But, what if you want to compare two objects of same concrete types? Thats practically a valid scenario, you would want to compare a Hatchback (concrete) type with another Hatchback and thats being prevented by swift compiler.

Solution: Type erasures

A Type erasure can be described as a type that erases the original type by encapsulating it in another concrete type that enables equality check. So basically, you create a concrete type EquatableVehicle and make it conform the generic type Vehicle and then you add an initialiser with a single parameter Vehicle . This concrete type would act as a proxy and returns the underlying object’s properties when invoked.

struct EquatableVehicle: Vehicle {var model: String {return vehicle.model}var brand: String {return vehicle.brand}private let vehicle: Vehicleinit(_ vehicle: Vehicle) {self.vehicle = vehicle}}

Now, you need to add an additional method to our generic protocol, so that we can enable comparison and add a default implementation for Vehicle types that conforms to Equatable

protocol Vehicle {var model: String { get }var brand: String { get }func isEqualTo(_ other: Vehicle) -> Bool}
extension Vehicle where Self: Equatable {func isEqualTo(_ other: Vehicle) -> Bool {guard let otherVehicle = other as? Self else { return false }return otherVehicle == self}}

And finally, you need to make our EquatableVehicle to conform to Equatable

extension EquatableVehicle: Equatable {static func == (lhs: EquatableVehicle, rhs: EquatableVehicle) -> Bool {return lhs.vehicle.isEqualTo(rhs.vehicle)}}

So far, all our concrete types that conforms to Vehicle and Equatable will get a default implementation of func isEqualTo(_ other: Vehicle) -> Bool , so does the EquatableVehicle type. So when you compare two EquatableVehicle types, the Equatable conformance forward the validation to isEqualTo(...) method. Where, the current object type(lhs) and passed in object type(rhs) are compared, if those two types are equal, then compare those objects for equality or return false, as we are trying to compare two different concrete types.

You could optionally add an additional convenience method to get the EquatableVehicle type, just in case you want to compare it.

protocol Vehicle {//...func asEquatable() -> EquatableVehicle}extension Vehicle where Self: Equatable {//...func asEquatable() -> EquatableVehicle {return EquatableVehicle(self)}}

Time for testing:

struct Hatchback: Vehicle, Equatable {var model: Stringvar brand: String}struct Suv: Vehicle, Equatable {var model: Stringvar brand: String}let hundaiI20: Vehicle = Hatchback(model: "Hundai", brand: "i20")let marutiBaleno: Vehicle = Hatchback(model: "Maruti", brand: "baleno")let anotherHundaiI20: Vehicle = Hatchback(model: "Hundai", brand: "i20")let anSuv = Suv(model: "Maruti", brand: "baleno")print(hundaiI20.asEquatable() == anotherHundaiI20.asEquatable()) //Trueprint(hundaiI20.asEquatable() == marutiBaleno.asEquatable()) //Falseprint(hundaiI20.asEquatable() == anSuv.asEquatable()) //False

Resources:

You can find the related playground resource here.

Kindly share your thoughts about this article in the comments section.

--

--

Saravanakumar S
Saravanakumar S

Written by Saravanakumar S

iOS App developer. Trying to be a better developer than yesterday.

No responses yet