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:
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:
case coffee(size: String)declares a case with a detail: the parentheses hold the labelsizeand the typeString.Order.coffee(size: "L")creates a coffee order and writes the size"L"on its ticket.secondis coffee too, but with the size"S": one case, different details.printshows 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.
1enum Order {2 case tea3 case coffee(size: String)4}5 6func label(_ order: Order) -> String {7 switch order {8 case .tea:9 return "tea"10 case .coffee(let size):11 return "coffee " + size12 }13}14 15print(label(.coffee(size: "L")))16print(label(.tea))17print(label(.coffee(size: "S")))
The program calls label and passes a coffee order of size L.
What to remember:
- the constant
sizeexists 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
switchis still exhaustive: if every case has a branch,defaultis not needed.
A pattern with where
You can add a where condition to a pattern with an associated value. A large coffee costs more:
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 3Let's walk through the program:
firsthas the size"L": the pattern.coffee(let size)fits, thewherecondition is true, and the price is4.secondhas the size"S": thewherecondition is false, and theswitchmoves on to the next branch.- The branch
case .coffee:accepts any coffee, and the price ofsecondis3.
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:
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 withswitchorif 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:
print(first.size) // ✗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 aswitchputs the value into a constant that exists only in that branch. if case .coffee(let size) = orderchecks 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.