From Swift in Depth by Tjeerd in ‘t Veen

This article, adapted from chapter 2 of Swift in Depth, discusses the “or” and “and,” also known or sum and product types, respectively, and how they can be used in Swift.

Save 37% on Swift in Depth. Just enter code fccveen into the discount code box at checkout at

Tag along! It’s more educational and fun if you can check out the code and follow along with the article. You can download the source code at:

Enums are based on something called algebraic data types, which is a term that comes from functional programming languages, where enums are sometimes referred to as sum types. It’s good to know these terms when talking in programming concepts. Enums—or sum types—can be thought of as an “or” type. Sum types can only be one thing at a time, e.g.: A traffic light can either be green or yellow or red. Or how a die can either be six-sided or twenty-sided, but not both at the same time. On the other end of the spectrum, we have another concept called product types. A product type is a type that contains multiple values, such as a class, tuple or struct. You can think of a product type as an “and” type. E.g. a User struct can have both a name and an id. Or an address class can have a street and a housenumber and a zipcode.

Modeling data with a struct

Let’s start off with an example that shows how to think about “or” and “and” types when modeling data.In the upcoming example we’re modelling message data in a chat application. A message could be text that a user may send, but it could also be a join or leave message. A message could even be a signal to send balloons across the screen. Why not; Apple does it in their Messages app.

Figure 1 A chat application.

If we’d list the types of messages that our application supports, we’d have:

  • A join message, such as “Mother-in-law has joined the chat.”

  • A text message that someone can write. Such as “Hello everybody!”

  • A send balloons message, which includes some animations and annoying sounds that others see and hear.

  • A leave message, such as “Mother-in-law has left the chat.”

  • A message which is being drafted, such as “Mike is writing a message.”

Let’s create a data model to represent messages. Our first idea might be to use a struct to model our Message, we’ll start by doing that and showcase the problems it brings. Then we’ll solve these problems by using an enum. We can create multiple types of messages in code, such as a join message when someone enters a chatroom.

Listing 1 A join chatroom message

 import Foundation // Needed for the Date type.
 let joinMessage = Message(userId: "1",
                         contents: nil,
                         date: Date(),
                         hasJoined: true, // We set the joined boolean
                         hasLeft: false,
                         isBeingDrafted: false,
                         isSendingBalloons: false)

We can also create a regular text message.

Listing 2 A text message

 let textMessage = Message(userId: "2",
                         contents: "Hey everyone!", // We pass a message
                         date: Date(),
                         hasJoined: false,
                         hasLeft: false,
                         isBeingDrafted: false,
                         isSendingBalloons: false)

In our hypothetical messaging app, we can pass this message data around to other users.

Our Message struct looks as follows:

Listing 3 The Message struct

 import Foundation
 struct Message {
     let userId: String
     let contents: String?
     let date: Date
     let hasJoined: Bool
     let hasLeft: Bool
     let isBeingDrafted: Bool
     let isSendingBalloons: Bool

Although this is one small example, it displays a problem. Because a struct can contain multiple values, we can run into bugs where the Message struct can both be a text message and a hasLeft command, as well as a isSendingBalloons command–which doesn’t work in this example, because a message can only be one or another in the business rules of the application. The visuals won’t support an invalid message either.

For example, we can have an invalid message that can be conflicting. It can show a text, but also a join and a leave message.

Listing 4 An invalid message with conflicting fields.

 let brokenMessage = Message(userId: "1",
                           contents: "Hi there", // We have text to show
                           date: Date(),
                           hasJoined: true, // But this message also signals a joining state
                           hasLeft: true, // ... and a leaving state
                           isBeingDrafted: false,
                           isSendingBalloons: false)

What tends to happen is that the more fields a struct contains, the more complex the struct becomes. The unique combinations of states grow exponentially higher with the number of fields added. The more fields you slap on a struct, the higher the chance of bugs.

In a small example it’s harder to run into invalid data, but it definitely happens often enough in real-world projects. Imagine a Message being created from a local file, or some function that combines two messages. There are no compile-time guarantees that a message is in the right state

We can think about validating a Message and throwing errors, but then we’re catching invalid messages at runtime (if at all). Instead, we can prevent this invalid message issue at compile time if we model the Message using an enum.

Turning a struct into an enum

Whenever you’re modeling data, see if you can find mutually exclusive properties. For example, a message can’t be both a join and leave message at the same time. A message can’t send balloons and also be a draft at the same time.

A message can be a join or leave message. A message can also be a draft or sending balloons. When you detect “or” statements in a model, an enum could be a more fitting choice for your data model.

Using an enum to group the fields into cases makes the data much easier to grasp. We can instantly see that there are five types of messages that we can send. Also, it’s clear which fields belong together and which fields don’t.

Let’s improve our model by turning it into an enum.

Listing 5 Message as an enum (lacking values)j

 import Foundation
 enum Message {
     case text
     case draft
     case join
     case leave
     case balloon

The total sum of variations of Message are five cases. Hence why enums are sometimes called sum types. But we’re not there yet, the cases have no values. We can add values by adding a tuple to each case.

A tuple is an ordered set of values. Such as (userId: String, contents: String, date: Date). By combining an enum with tuples we can build more complex data structures. Let’s add tuples to the enum’s cases now:

Listing 6 Message as an enum, with values

 import Foundation
 enum Message {
     case text(userId: String, contents: String, date: Date)
     case draft(userId: String, date: Date)
     case join(userId: String, date: Date)
     case leave(userId: String, date: Date)
     case balloon(userId: String, date: Date)

By adding tuples to cases, these cases now have associated values in Swift terms. Structs, tuples, and classes are a product type. This is because the product is the total number of variations a product type can hold, e.g. the number of string variants (for userId) times the number of string variants (for contents) times the number of dates = the product.

Now whenever we want to create a Message as an enum, we can pick the proper case with related fields. It’s impossible to mix and match the wrong values.

Listing 7 Creating enum messages

 let textMessage = Message.text(userId: "2", contents: "Bonjour!", date: Date())
 let joinMessage = Message.join(userId: "2", date: Date())

When we want to work with the messages, we can use a switch case on them and unwrap its inner values.

For example, we can log the messages which have been sent:

Listing 8 Logging messages

 logMessage(message: joinMessage) // User 2 has joined the chatroom
 logMessage(message: textMessage) // User 2 sends message: Bonjour!
 func logMessage(message: Message) {
     switch message {
     case let .text(userId: id, contents: contents, date: date):
         print("[\(date)] User \(id) sends message: \(contents)")
     case let .draft(userId: id, date: date):
         print("[\(date)] User \(id) is drafting a message")
     case let .join(userId: id, date: date):
         print("[\(date)] User \(id) has joined the chatroom")
     case let .leave(userId: id, date: date):
         print("[\(date)] User \(id) has left the chatroom")
     case let .balloon(userId: id, date: date):
         print("[\(date)] User \(id) is sending balloons")

It may be a deterrent having to switch on all cases in your entire application to read a value from a single message. You can save yourself some typing by using the if case let combination to match on a single type of Message.

Listing 9 Matching on a single case

 if case let Message.text(userId: id, contents:
     contents, date: date) = textMessage {
     print("Received: \(contents)") // Received: Bonjour!

If we’re uninterested in certain fields when matching on an enum then we can match on these fields with an underscore, or how I like to call it: the “don’t care” operator.

Listing 10 Matching on a single case with the “I don’t care” underscore.

 if case let Message.text(_, contents: contents, _) = textMessage {
     print("Received: \(contents)") // Received: Bonjour!

Next time you write a struct, see if you can try and group properties. Your data model might be a good candidate for an enum!


 1. Given this struct, does it represent a remote image well?
     Can we get compiler benefits by turning it into an enum? If so, change it to an enum.
 struct RemoteImage {
   let isLoading: Bool
   let isCached: Bool
   let path: String
   let resolution: Int
   let data: Data
 2. Given this struct, does it represent a friend type well?
     Can we get compiler benefits by turning it into an enum? If so, change it to an enum.
 struct Friend {
   let firstName: String
   let lastName: String
   let dateOfBirth: Date
   let twitterHandle: String
   let instagramHandle: String
   let facebookProfileURL: String
That’s all for now.

If you want to learn more about the book, check it out on liveBook here and see this slide deck.