Bad practice: not using Phantom Types


You’re more of a video kind of person? I’ve got you covered! Here’s a video with the same content than this article 🍿


Can you guess what’s the problem with this code?

I’ve defined a struct called Person, inside which I store a name and an id.

You can see that I’m using a UUID to represent the id, which guarantees uniqueness.

Now let’s introduce a second struct, called Location.

I’m following the same approach and I also use a UUID to represent the id of that new struct.

For now, this code seems pretty reasonable, doesn’t it?

Now let’s introduce a function that operates over an Array of Location.

You can notice that there is nothing preventing me from filtering the array of Location using the id of a Person.

And that’s a problem, because such code doesn’t make any sense and is guaranteed to lead to a bug!

However, there’s an easy way to improve!

We can introduce a new struct called ID.

In this struct, we’ll store a UUID.

And we’re also going to add a generic argument to this struct.

This might seem weird, because we’re not using this generic argument anywhere, but trust me: it will all make sense in a second!

Because now, we’re going to remove the UUID from both Person and Location

…and replace them with our new ID type.

If we look at the type of each id property, we can see that, thanks to the extra generic argument, the types are no longer the same!

This means that if by mistake we attempt to compare the id of a Person with the id of a Location, we’ll get a compile-time error and it will be impossible to ship this incorrect code!

That’s all for this article, I hope you’ve enjoyed learning this new trick!

Here’s the code if you want to experiment with it:

import Foundation

struct ID<T>: Equatable {
    private let value = UUID()
}

struct Person {
   let id = ID<Self>() // `id` is of type `ID<Person>`
   let name: String
}
struct Location {
   let id = ID<Self>() // `id` is of type `ID<Location>`
   let coordinates: (Double, Double)
}

func handle(locations: [Location]) {
    let me = Person(name: "Vincent")
    let filtered = locations.filter { $0.id == me.id } // compile-time error
}
Previous
Previous

This small mistake will crash your SwiftUI app 🧨

Next
Next

Here are 8 tips to optimize your iOS app 📱