Skip to content
Theory · Enums · lesson 4 of 8

Associated values

Let's go back to the café menu. Tea is always served the same way, but coffee comes in different sizes. The waiter writes on the ticket: "coffee, size L". The menu line is the same for everyone, but each order's ticket carries its own details.

A raw value does not help here: there is one per case, and it is written in the code. The size is chosen by the guest when they order. For details like these, cases have associated values:

SWIFT
enum Order {
    case tea
    case coffee(size: String)
}

let first = Order.coffee(size: "L")
let second = Order.coffee(size: "S")
print(first) // coffee(size: "L")
print(second) // coffee(size: "S")

Let's go through the program line by line:

  1. case coffee(size: String) declares a case with a detail: the parentheses hold the label size and the type String.
  2. Order.coffee(size: "L") creates a coffee order and writes the size "L" on its ticket.
  3. second is coffee too, but with the size "S": one case, different details.
  4. print shows the case together with its details, and a string among the details in quotes, as it is written in code.

tea has no parentheses: tea is ordered without details.

Now an enum has more values than cases: first and second are two different values of the one case coffee.

Declaring and creating

An associated value is a value stored together with a case and set anew each time a value of the enum is created. What to know about it:

  • its label and type go in parentheses after the case name: coffee(size: String);
  • when you create a value, you pass it like a function argument: .coffee(size: "L");
  • a case can have several associated values of different types: juice(fruit: String, glasses: Int);
  • one case may have them while another has none at all, like tea.

Getting the value out in switch

You get an associated value out with a pattern in a switch: case .coffee(let size):. The pattern fits any coffee and puts the size from its ticket into a new constant size. It is the same let pattern as in the conditions theme, only written inside the case's parentheses.

Press "Next step" and watch the constant size: on each call it holds the size of that call's order.

LiveHow switch gets the size outstep 1 of 11 · frames: 1

The program calls label and passes a coffee order of size L.

Watch size: it appears only in the .coffee branch

What to remember:

  • the constant size exists only inside its branch, and you can pick any name for it: case .coffee(let s):;
  • the pattern case .coffee: without parentheses also fits any coffee, but does not get the size out;
  • the switch is still exhaustive: if every case has a branch, default is not needed.

A pattern with where

You can add a where condition to a pattern with an associated value. A large coffee costs more:

SWIFT
func price(_ order: Order) -> Int {
    switch order {
    case .tea:
        return 2
    case .coffee(let size) where size == "L":
        return 4
    case .coffee:
        return 3
    }
}

print(price(first), price(second)) // 4 3

Let's walk through the program:

  1. first has the size "L": the pattern .coffee(let size) fits, the where condition is true, and the price is 4.
  2. second has the size "S": the where condition is false, and the switch moves on to the next branch.
  3. The branch case .coffee: accepts any coffee, and the price of second is 3.

The order of the branches matters here. A switch runs the first branch that fits, so case .coffee: above the where branch would take all the coffee.

One branch: if case

When you need only one case, writing a switch with every branch takes long. For that there is if case:

SWIFT
let order = Order.coffee(size: "M")
if case .coffee(let size) = order {
    print("size", size) // size M
}

It reads like this: "if order fits the pattern .coffee(let size)". The block runs only when order holds coffee. The constant size exists inside the block. It is similar to if let, and you can add an else branch here too.

Raw or associated

Interviewers like to ask this: how does a raw value differ from an associated value? The answer:

  • there is one raw value per case, fixed in the declaration, while an associated value is set anew each time a value is created;
  • raw values have one type for all cases, while associated values are each case's own;
  • Dish(rawValue:) finds a case by its raw value, while an associated value is taken out with switch or if case.

You cannot combine them. The declaration enum Dish: Int with the case pasta(size: Int) does not compile: enum with raw type cannot have cases with arguments.

Here the ticket picture is inaccurate. A waiter can add anything to a ticket, even "no sugar". An associated value has its label and type set in the declaration: only a string goes into size, and you cannot add a size to tea.

Common mistake

The size was read with a dot, like count on a string:

SWIFT
print(first.size) // ✗
TEXT
error: value of type 'Order' has no member 'size'

The program does not compile. size is a detail of the case coffee, not of every order: tea has none. Until you check that first holds coffee, the compiler will not let you read the size. Get it out with switch or if case.

Key points

  • An associated value is stored together with a case and set when the value is created: .coffee(size: "L").
  • The pattern case .coffee(let size): in a switch puts the value into a constant that exists only in that branch.
  • if case .coffee(let size) = order checks for one case and gets its value out.
  • A raw value is one per case and fixed in the declaration, while each value has its own associated value; one enum never combines the two.