Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

array
An ordered list of values of one type, reached by position starting at zero.
block
A group of lines of code that run together, often inside curly brackets.
closure
A piece of code you can store in a value and pass to other code, remembered with the names around it.
compiler
The program that turns the code you write into something the machine can run, reporting mistakes before it does.
dictionary
A collection of key and value pairs, where each key appears once and finds its value.
enum
A type whose value is one of a fixed set of named cases, which may each carry extra data.
expression
A piece of code that produces a value, such as a sum or a call to a function.
function
A named piece of code that takes values and returns a result when called.
guard
A check that must pass before the code after it runs, leaving the scope early when it fails.
method
A function that belongs to a type and is called on a value of that type.
nil
The absence of a value, allowed only where a type says a value may be missing.
operator
A symbol such as plus or equals that combines values into a new one.
optional
A type that either holds a value or holds nothing, written with a question mark after the type.
optional chaining
Writing a question mark before something you reach into, so the result is nil when the value is missing.
property
A named value that belongs to a type or to one of its values.
protocol
A list of requirements that a type promises to meet, so code can work with any type that meets them.
scope
The region of code where a name can be used before it goes out of reach.
string interpolation
Putting a value inside text with a backslash and parentheses, so the value becomes part of the text.
struct
A type that groups related values and makes a copy whenever it is given another name or passed along.
subscript
A way to read or write an item in an array or dictionary by putting a key or position in square brackets.
type
What kind of value something is, such as a whole number or text, and what it can do.
unwrap
To get the value out of an optional after checking that something is there.

Swift from the ground up

Tuples and enums

Grouping a few values without a name, and a fixed set of cases where each case can carry its own data.

Part 9 of 12

Advanced18 min readBack to Courses

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.

Output
Click "Run" to execute your code

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 with init?(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.

Output
Click "Run" to execute your code

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?