Skip to content

Tuples

In this chapter, you’ll learn how to group multiple values into a single compound value known as a tuple.

You create a tuple by listing its elements, separated by commas and enclosed in parentheses. For example:

swift
var point = (4.3, -13.7)

This tuple represents a two-dimensional point. Its type is (Double, Double).

A tuple is heterogeneous, meaning its elements can be of different types:

swift
var response = (404, "Not Found")

The type of this tuple is (Int, String).

You can use any type in a tuple, even another tuple type.

Elements

A tuple assigns its elements an index, starting at index 0 for the first element. You use this index, preceded by a dot, to access the elements:

swift
print(response.0)
point.1 = 2.7

Of course, you can’t mutate a tuple’s elements if the tuple is a constant:

swift
let origin = (0.0, 0.0)
origin.0 = -1.0  // Invalid!

Element names

You can name a tuple’s elements to clarify their meaning:

swift
var secondPoint = (x: 5.7, y: 1.8)

This lets you access the elements by name:

swift
secondPoint.x = 2

Element names are part of a tuple’s type. The type of secondPoint is (x: Double, y: Double), not just (Double, Double). Fortunately, these types are compatible.

The following assignment is allowed:

swift
secondPoint = point

This assigns point.0 to secondPoint.x and point.1 to secondPoint.y.

The reverse is also allowed:

swift
point = secondPoint

This assigns secondPoint.x to point.0 and secondPoint.y to point.1.

However, tuples with different element names aren’t compatible because Swift assumes they have a different meaning:

swift
var measurement = (width: 1.2, height: 5.6)
secondPoint = measurement  

The next section describes a technique you can use to perform this assignment differently.

Decomposition

You can decompose a tuple into its elements:

swift
var (code, message) = response

This declares two variables, code and message, and assigns response.0 to code and response.1 to message.

If you want to assign only some of a tuple’s elements, you can use the wildcard pattern (_) to skip the elements you don’t want:

swift
(_, message) = response

This assigns only response.1 to message.

You can use decomposition to assign tuples with conflicting element names:

swift
(secondPoint.x, secondPoint.y) = measurement

This assigns measurement.0 to secondPoint.x and measurement.1 to secondPoint.y. The element names of measurement don’t matter here because you’re decomposing the tuple and assigning its elements separately.

You can achieve the same result as follows:

swift
secondPoint = (measurement.width, measurement.height)

This creates a new tuple containing the elements of measurement and assigns this tuple to secondPoint. The new tuple doesn’t have element names, so its type is compatible with secondPoint.

Comparison operators

You can use the familiar comparison operators to compare tuples. Set point to its initial value of (4.3, -13.7), then try the following:

swift
point == (4.3, -13.7)
point < (6.4, -15.6)
point > (4.3, -15.6)

These operators perform their comparisons from left to right:

  • In the first expression, the operator first compares point.0 and 4.3, then point.1 and -13.7. Both pairs are equal, so the operator returns true.
  • In the second expression, the operator returns true because point.0 is less than 6.4, regardless of the remaining elements.
  • In the third expression, the operator first compares point.0 and 4.3. These are equal, so the operator moves to the second pair of elements. point.1 is greater than -15.6, so the operator returns true.

The comparison operators are less strict than the assignment operator in that they ignore element names. So you can, for example, check if secondPoint and measurement contain the same values:

swift
secondPoint == measurement

Switching over tuples

A switch statement can test all of the elements in a tuple at once. For example:

swift
switch point {
case (0, 0):
  print("This point is the origin.")
case (_, 0):
  print("This point is on the x-axis.")
case (0, _):
  print("This point is on the y-axis.")
default:
  break
}

This example uses the tuple pattern. As its name implies, this pattern consists of a tuple of patterns. For example, the tuple pattern (0, _) consists of an expression pattern (0) and the wildcard pattern (_). In the example, the switch statement decomposes point and matches each element against the corresponding pattern.

The next example uses the tuple pattern to implement the currentPlayerHasWon function from Tic-Tac-Toe:

swift
func currentPlayerHasWon() -> Bool {
  switch (currentPlayer, currentPlayer, currentPlayer) {
  case (topLeft, top, topRight),
       (left, center, right),
       (bottomLeft, bottom, bottomRight),
       (topLeft, left, bottomLeft),
       (top, center, bottom),
       (topRight, right, bottomRight),
       (topLeft, center, bottomRight),
       (topRight, center, bottomLeft):
    true
  default:
    false
  }
}

Note

This function has an implicit return statement that returns the result of a switch expression.

This code checks if any of the rows, columns, or diagonals are filled with the current player’s symbol. The first case contains a list of tuple patterns that group the values in each row, column, or diagonal. The switch statement matches these patterns against a tuple that contains three copies of the player’s symbol.

The tuple pattern is often combined with the value-binding pattern. A value binding uses let or var to assign (bind) a local name to a matched value. For example, the binding let x matches any value and assigns it the name x. Cases that use value binding often include a where clause to restrict their matches.

Here’s an example:

swift
switch point {
case (0, 0):
  print("This point is the origin.")
case (let x, 0):
  print("This point is on the x-axis at x=\(x).")
case (0, let y):
  print("This point is on the y-axis at y=\(y).")
case let (x, y) where x > 0 && y > 0:
  print("This point is in the first quadrant at x=\(x) and y=\(y).")
case let (x, y) where x < 0 && y > 0:
  print("This point is in the second quadrant at x=\(x) and y=\(y).")
case let (x, y) where x < 0 && y < 0:
  print("This point is in the third quadrant at x=\(x) and y=\(y).")
case let (x, y) where x > 0 && y < 0:
  print("This point is in the fourth quadrant at x=\(x) and y=\(y).")
default:
  break
}

This example shows how you can combine the value-binding pattern with the tuple pattern:

  • (let x, 0) and (0, let y) are tuple patterns that have a value-binding pattern as one of their elements.
  • let (x, y) is a value-binding pattern that uses a tuple pattern to bind two values. In this case, the keyword let applies to both x and y.

Tuples as return values

You can use a tuple to return multiple values from a function. Here’s a function that returns the width and height of a rectangle formed by two given points:

swift
func sizeOfRectangle(
  from corner1: (x: Double, y: Double),
  to corner2: (x: Double, y: Double)
) -> (width: Double, height: Double) {
  let width = abs(corner2.x - corner1.x)
  let height = abs(corner2.y - corner1.y)
  return (width, height)
}

Note

This example uses a different formatting style to improve the readability of the lengthy function declaration.

This function uses tuples for both its parameters and its return type. You call it as follows:

swift
let size = sizeOfRectangle(from: (-2, 2), to: (1, -1))

The inferred type of size is (width: Double, height: Double), so you can access its elements by name:

swift
size.width
size.height

Type aliases

If you often use the same tuple type, you can declare a type alias for it:

swift
typealias Point = (x: Double, y: Double)
typealias Size = (width: Double, height: Double)

This declares Point as an alias for the type (x: Double, y: Double), and Size as an alias for (width: Double, height: Double).

typealias creates shorthands for complex types and introduces type names that are specific to your application. A type alias doesn’t add any functionality, but it can make your code clearer and less verbose:

swift
func sizeOfRectangle(from corner1: Point, to corner2: Point) -> Size {
  let width = abs(corner2.x - corner1.x)
  let height = abs(corner2.y - corner1.y)
  return (width, height)
}

In Part IV, you’ll learn how you can declare your own custom types that are more than aliases for existing types.

Up next

In this chapter, you learned how you can use tuples to group values. Tuples have many uses; however, their number of elements is fixed when you declare them. This means you can’t use a tuple to, for example, keep track of the movies you want to watch.

In Arrays and Dictionaries, you’ll learn about types that represent a collection of elements. These collections can grow to accommodate any number of elements you want to store.

Before you can learn about collections, you first need to learn about optionals. This is the topic of the next chapter.