DEV Community

Cover image for Strongly Typed Identifiers
Martin Arista
Martin Arista

Posted on

3 1

Strongly Typed Identifiers

This post originally appeared on my blog mrtnrst

Have you ever typed a word so many times it looks misspelled? As someone who types for a living, I tend to have trouble with this. The beauty of a misspelled word in programming is consistency. If it’s wrong everywhere, it’s right- right? However, loosely typed identifiers can lead to problems down the road for both our future selves and other code maintainers.

Starting with Colors

With most UI/UX programs, we tend to work with color, generally stemming from a style guide. Let us consider this function:

func colorView(with bgColor: UIColor) -> UIView { }

This color view can be anything we want it to be but our guide says we can only use the colors given to us. Rather than having to remember an RGB value to create each time, let’s make it easier on ourselves.
With a small bit of setup, we can fix something that is prone to errors. We can utilize structs to achieve this.

struct ColorStyles {
static let tomato = UIColor(red: 255, green: 91, blue: 71, alpha: 1.0)
}
// usage
colorView(with: ColorStyles.tomato)

This is one of the simple use cases of a strongly typed identifier. The goal of this is to make sure we keep avoidable errors to a minimum.


Graduating to Strings

Another pain point and source of frustration is when strings are used all willy-nilly. For instance, this function here:

func findFoo(with id: String) { }

A few things could happen when using this function. To name a few: misspelling of the id parameter, finding a different object with the same id or causing a crash when inside the function.

So let us take a second and strongly type these functions. We want to create a struct so we can be explicit and readability is at the highest.

struct Foo {
typealias Identifier = String
let id: Identifier
}
// Updating our func
func findFoo(with id: Foo.Identifier) { }
// Usage
findFoo(with: foo.id)
findFoo(with: “123”) // This still works! 🙃

Generics, Structs, and Strongly Typed Identifiers

More wisdom from Edna.Even with the extra struct, both uses still pass the compiler. Do you know why our function still accepts these values? As with all types, a String is a String no matter the alias. How about we change the type alias into something that is unique to that struct?

This is when we implement the protocols RawRepresentable and Equatable and sprinkle in Generics to create this:

struct GenericIdentifier<T>: RawRepresentable, Equatable {
let rawValue: String
init(rawValue: String) {
  self.rawValue = rawValue
  }
}
struct Bar {
typealias Identifier = GenericIdentifier<Bar>
let id: Identifier
}
func findBar(with id: Bar.Identifier) { }

As for the usage of this new and improved function:

let barID = Bar.Identifier.init(rawValue: “123”)
let bar = Bar.init(id: barID)
findBar(with: bar.id)
findBar(with: “123”) // this will error 😎

Voila!

We have now locked up our function and kept the errors at bay. There are many different ways you can keep function associations close and organized. My advice to you is to start with low hanging fruit and keep building.

Sentry mobile image

App store rankings love fast apps - mobile vitals can help you get there

Slow startup times, UI hangs, and frozen frames frustrate users—but they’re also fixable. Mobile Vitals help you measure and understand these performance issues so you can optimize your app’s speed and responsiveness. Learn how to use them to reduce friction and improve user experience.

Read full post →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay