UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Using two-cased enums in place of a Boolean

A little extra clarity goes a long way

Paul Hudson       @twostraws

Booleans are the simplest data type possible, storing either true or false – a simple binary one or zero. However, recently I’ve been trying to replace them with two-cased enums and I’ve found my code has become clearer as a result.

This was inspired by an article I read by Maciej Konieczny, which came to my attention when Jon Reid tweeted about it.

If you listen to the Swift over Coffee podcast I record with Sean Allen, you’ll know I’ve been trying this out for almost two months now. That might seem like a long time, but I wanted to really give it a thorough trial because it’s such a fundamental change.

My conclusion is pretty simple: most of code reads better when using two-cased enums. We’ll look at the advantages in more detail in a moment, but first let’s look at some examples from the code I was using:

// old
var isUserCorrect: Bool
isUserCorrect = true

// new
enum AnswerState {
    case correct
    case wrong
}

var answerState: AnswerState
answerState = .correct

// old
var containsNewArticles: Bool
containsNewArticles = true

// new
enum ArticleFeedState {
    case newArticles
    case nothingNew
}

You get the idea: rather than having a Boolean true or false, replace it with a two-cased enum instead.

Tip: You might look at case wrong and think that maybe you'd use case incorrect instead. Both are valid, but I ended up with wrong through trial and error – I tried both to see what felt good, and what I found is that correct and incorrect are visually so close that sometimes I was misreading lines.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

But why bother?

Admit it: this is the question you’ve been asking since you started reading. After all, Booleans sort of already are two-cased enums – they can be true or false.

What Maciej argues – and I can confirm having tried it myself for about eight weeks now! – is that switching to a two-cased enum changes three things.

First, enums are really explicit about what you mean when you use them. For example, the isHidden property of UIView is fine when we’re checking for it positively – i.e. if someView.isHidden – but becomes less clear when we negate that to say if !someView.isHidden. If that had been a two-case enum then the result would have been more explicit code: someView.display = .visible, or something similar.

This becomes even more important when you fall into what’s sometimes called the Boolean trap: when you pass an unnamed Boolean value into a method as a parameter. For example, the stopAnimation() method on UIViewPropertyAnimator does exactly this, so when you see animator.stopAnimation(false) you might not be sure what it means. In this instance, it refers to whether any final actions should be performed or not, so if we had used something like animator.stopAnimation(.performingFinalActions) it would have been clearer. (Of course, naming the parameter would have been even better!)

We all know that code is read far more often than it’s written, so it’s places like these that make two-cased enums so useful.

The second point Maciej makes is that enums give us room to grow over time. While you might have started off just needing true or false, over time you might find you want a third or fourth case to represent different states.

For example, that ArticleFeedState enum I mentioned earlier might have started off with just newArticles and nothingNew, but we could easily grow it to fetchFailed or fetchInProgress.

Now, we could represent both of those in the Boolean: maybe containsNewArticles as false means that the fetch failed or it’s in progress or there really are no new articles, but it’s not as clean a solution. Alternatively, we could add multiple enums to represent the possible states, but then we end up needing to write a complicated series of test to cover all cases.

Maciej’s final point is that enums are safer, partly because Swift forces switch statements to be exhaustive, and partly because it stops us making lazy assignments.

In the case of exhaustive switch statements, it means the compiler will warn us of all the places our enum is being used, so when we add a case we’ll be forced to consider all the places where it’s used – or at least rely on some sensible default behavior we made earlier.

As for the assignments, this isn’t a trap I’ve fallen into for some time, but in the past I can certainly imagine having written code like this:

game.isPlaying = user.isAuthenticated

That code itself is perfectly valid – “the game is playing if the user is authenticated” – but it’s the kind of thing that is brittle enough to break in the future.

Two-cased enums stop this kind of thing dead in the water, because as far as Swift is concerned the two enums are completely different types – you can’t assign a thing of type A to a thing of type B.

Two-cased enums don’t make this impossible, but they do need to make your intention clearer:

if user.authentication == .loggedIn {
    game.mode = .playing
}

To switch or not…

Two-cased enums won’t work everywhere, so as usual be pragmatic when trying them out. For example, someString.startsWithVowel can only ever be true or false, so there’s no point overcomplicating things.

The smart thing to do here is pick one or two Booleans in your project and try it out: make them into two-cased enums and see whether the change was an improvement or not. Yes, I’ve found there was a real boost in clarity in my own code, and of course there’s no meaningful performance difference, but the only real test is if you try it in your own code.

So, give it a try in your own code and let me know how you get on – tweet me @twostraws and let me know what you think!

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.