Tuples and enums
Grouping a few values without a name, and a fixed set of cases where each case can carry its own data.
A few values travelling together
A tuple packs several values into one. (x: 3, y: 4) holds two numbers under the names x and y, and you reach them with a dot. It is the quick tool for returning two answers from a function without inventing a whole type.
You can also take a tuple apart in one line. let (low, high) = minMax(values) lays the pieces straight into two names.
A tuple can be read by position as well as by name. point.0 and point.x reach the same value when the tuple was written with labels. The labels do not change the value; they are part of how this tuple was built, so a function that promises (min: Int, max: Int) should return a tuple with those labels to keep the promise readable.
A fixed set of cases
An enum names a small, closed set of possibilities. enum Direction { case north, south, east, west } says a direction is exactly one of those four and nothing else.
A switch over an enum must list every case, and the compiler checks that you did. Adding a new case later then shows you every place that needs revisiting.
Once the type is known, the case name can be written with a leading dot: let heading: Direction = .north. The compiler fills in Direction from the annotation. Without an annotation you must write the full name, because .north alone does not say which enum it belongs to.
Cases that carry their own data
A case can hold values of its own. case cash(amount: Double) only makes sense with an amount attached, and case card(amount: Double, last4: String) carries two pieces.
In a switch you open the carried values with let, so each arm gets exactly the data that case holds. This is how one type can describe several differently shaped things without any of them being empty.
A single case can be tested without a full switch: if case .cash(let amount) = payment runs its block only for that shape. The same pattern works in a guard case, which is useful at the top of a function that can only handle one kind of payment. A switch is still clearer when several shapes need different work.
Raw values are fixed labels
An enum can store a raw value beside each case: enum Suit: String { case hearts = "H" }. The raw value is fixed when the program is compiled and read with .rawValue. It is not the same as an associated value, which belongs to one instance and can differ each time.
A raw-value enum can also be built from its raw value with init?(rawValue:). That initialiser returns an optional because the text you pass may not match any case.
Raw values must be unique within an enum, and the compiler rejects a duplicate. An Int raw type numbers the cases from zero when you do not write the values, while a String raw type uses each case name. The raw value is fixed at compile time, so it cannot depend on the instance the way an associated value can.
Choosing the right shape
A tuple is best for two or three values that travel together briefly, especially as a return value. It needs no declaration, and the labels make the call site readable. It becomes awkward when the group grows, when it needs methods, or when it must be stored and passed through several layers, because every user has to remember the field order.
A struct is the better home for data that has a name and behaviour. An enum is the right choice when a value must be one of a known set of shapes, especially when the shapes carry different data. Reaching for an enum early turns a string full of special cases into a switch the compiler can check.
A tuple and a plain enum
let point = (x: 3, y: 4)
print(point.x, point.y)
func minMax(_ values: [Int]) -> (min: Int, max: Int) {
return (values.min()!, values.max()!)
}
enum Direction {
case north, south, east, west
}
let heading: Direction = .north
switch heading {
case .north:
print("up")
default:
print("other")
}Three mistakes blur the two ideas. A raw value is not an associated value: an enum with a raw type cannot also carry values in its cases, and .rawValue exists only for the raw type. A switch over an enum should list every case rather than lean on default, so a new case produces an error where it must be handled. And a tuple held in a let cannot have one element changed, even though it looks like a small record.
Payments of two kinds
Run it and read the two lines of the report. Then add a third payment of the cash kind and predict what prints before you run it again.
Try these next
- Return a labelled tuple of two answers from a function and read both by name.
- Give an enum a raw type, print each case’s
rawValue, and build one back withinit?(rawValue:). - Add a case to an enum and follow the compiler errors to every switch that must handle it.
A summary tuple and a status
Run it and check the two tuple values. Then add a word to the text and a case to the enum, and see which parts of the program need updating.
Reach for a tuple for two or three values that are used together briefly, and for an enum when a value must be one of a known handful of shapes. Both say what the data is instead of leaving it to a comment.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What is a tuple good for?
