iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Tuples

A Swift tuple groups multiple values of any type into a single compound value. Useful for ad-hoc returns, multi-value bindings, dictionary iteration. No need to declare a struct for one-off shapes.

Create, destructure, label, return

EXAMPLE
// 1) Create — ad-hoc grouping
let point = (x: 10, y: 20)
let person = ("Ada", "Lovelace", 32)            // unnamed
let status = (code: 200, message: "OK")

// Type annotation
let result: (Int, String) = (42, "success")

// 2) Access — by index or label
print(point.x)                                   // 10
print(point.y)                                   // 20
print(person.0)                                  // 'Ada'
print(person.1)                                  // 'Lovelace'
print(person.2)                                  // 32
print(status.code, status.message)               // 200 OK

// 3) Destructure
let (firstName, lastName, age) = person
print(firstName, age)

// Ignore a value with _
let (_, last, _) = person
print(last)

// 4) Return multiple values from a function
func parse(_ s: String) -> (String, Int)? {
    let parts = s.split(separator: ":")
    guard parts.count == 2, let port = Int(parts[1]) else { return nil }
    return (String(parts[0]), port)
}

if let (host, port) = parse("localhost:8080") {
    print(host, port)
}

// 5) Labelled return — names + types
func divmod(_ a: Int, _ b: Int) -> (quotient: Int, remainder: Int) {
    (a / b, a % b)
}

let result = divmod(17, 5)
print(result.quotient)                           // 3
print(result.remainder)                          // 2

// 6) Comparable / Equatable — generated when components are
let a = (1, 2)
let b = (1, 2)
print(a == b)                                    // true

let pairs = [(1, 'a'), (2, 'b')]                 // [(Int, String)]

// 7) Use in switch / pattern matching
let code: (Int, String) = (200, "OK")

switch code {
case (200, _):           print("Success")
case (404, _):           print("Not found")
case (500...599, _):     print("Server error")
case (_, "OK"):          print("Generic OK")
case (let status, let msg): print("\\(status): \\(msg)")
}

// Bind matched values
let point = (3, 4)
switch point {
case (0, 0):                print("origin")
case (_, 0):                print("on x axis")
case (0, _):                print("on y axis")
case (let x, let y) where x == y:
    print("on diagonal at \\(x)")
case (let x, let y):
    print("point (\\(x), \\(y))")
}

// 8) Tuples in collections
let coords = [(0, 0), (1, 1), (2, 2)]
for (x, y) in coords {
    print("\\(x), \\(y)")
}

let dict = ["a": 1, "b": 2, "c": 3]
for (key, value) in dict {                       // each iteration is a tuple
    print("\\(key) = \\(value)")
}

// 9) Tuples in higher-order functions
let pairs = [("Ada", 32), ("Bo", 28), ("Cy", 41)]
let names = pairs.map(\\.0)                       // ['Ada', 'Bo', 'Cy']
let ages  = pairs.map { $0.1 }                  // [32, 28, 41]
let sorted = pairs.sorted { $0.1 < $1.1 }     // by age

// 10) Zip — combine arrays into pairs
let names = ["Ada", "Bo", "Cy"]
let ages  = [32, 28, 41]
for (name, age) in zip(names, ages) {
    print("\\(name): \\(age)")
}

let pairs = Array(zip(names, ages))              // [(String, Int)]

// 11) Tuples vs structs — when to use which

// Tuple — local, ad-hoc, throwaway
func minMax(_ nums: [Int]) -> (min: Int, max: Int) {
    (nums.min()!, nums.max()!)
}

// Struct — exposed in your API, reused, has methods or invariants
struct Range {
    let min: Int
    let max: Int
    var size: Int { max - min }
}

// Use tuple for internal helpers; struct for public APIs.

// 12) Limitations
//   ❌ Can't conform to protocols (use struct)
//   ❌ Can't have methods or computed properties
//   ❌ No initializer with custom logic
//   ❌ No subclasses
//   ❌ Recursion limit (tuples can't recursively contain themselves)

// 13) Tuple types
typealias Point = (x: Double, y: Double)
typealias Range = (min: Int, max: Int)

func midpoint(_ a: Point, _ b: Point) -> Point {
    ((a.x + b.x) / 2, (a.y + b.y) / 2)
}

// 14) Common patterns

// a) HTTP response
func fetch(_ url: URL) -> (data: Data?, error: Error?) {
    do {
        let data = try Data(contentsOf: url)
        return (data, nil)
    } catch {
        return (nil, error)
    }
}

// Better: use Result type for one-or-the-other
func fetchModern(_ url: URL) -> Result<Data, Error> {
    do { return .success(try Data(contentsOf: url)) }
    catch { return .failure(error) }
}

// b) Pagination cursor
func page(_ items: [Item], cursor: String?) -> (items: [Item], next: String?) {
    let batch = Array(items.prefix(20))
    let next  = items.count > 20 ? items[20].id : nil
    return (batch, next)
}

// c) Compare with tuple comparison
let versions = [(1, 0, 3), (1, 2, 0), (1, 0, 5), (2, 0, 0)]
let sorted = versions.sorted { $0 < $1 }
// Lexicographic compare — tuples are Comparable if components are.

// 15) Dictionary iteration
let inventory = ["apple": 5, "banana": 3, "cherry": 12]
for (name, count) in inventory where count > 5 {
    print("\\(name): plenty (\\(count))")
}

// Transform via map
let doubled = inventory.map { (name, count) in (name, count * 2) }

// 16) Nested tuples
let snapshot: ((Int, Int), [String]) = ((100, 200), ["a", "b"])
let ((x, y), tags) = snapshot

// 17) Empty / unit tuple
let nothing: () = ()                              // 'Void' is a typealias for ()
func say() -> () { print("hi") }                  // returns ()
func say2() { print("hi") }                       // same; () return implicit

// 18) Common bugs
//   • Confusing positional access — use labelled tuples to avoid index mistakes
//   • Returning a tuple instead of a Result for failure cases
//   • Long tuples (5+) — switch to a struct for readability
//   • Trying to add methods to tuples → compile error
//   • Comparing differently-shaped tuples → compile error

// 19) Best practices
//   ✅ Label tuple fields for clarity
//   ✅ Use typealias for repeated tuple types
//   ✅ Use tuples for LOCAL multi-value returns
//   ✅ Use structs when:
//      - Exposed in public API
//      - Has methods or invariants
//      - More than 3-4 fields
//      - Need to conform to a protocol
//   ✅ Use Result<T, Error> instead of (T?, Error?)
//   ✅ Destructure in for / if let bindings

Why it matters

Tuples shine for ad-hoc multi-value returns and dictionary iteration. Label fields for clarity, use typealias for repeated shapes — but graduate to a struct as soon as you need methods, protocol conformance, or more than 3-4 fields.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
let user = (name: "Ada", age: 36)
print(user.name, user.age)
Try it Yourself »

Discussion

Loading…