A Beginner Guide to Pattern Matching in Swift

Mar 17, 2023#patterns#swift

In Swift, pattern matching is a powerful feature that can be used to match values against a set of patterns, including ranges, optionals, tuples, and enums. With pattern matching, you can write more concise and expressive code that is easier to read and maintain.

Pattern matching is commonly used in control flow statements such as switch and if statements, but it can also be used in other contexts such as function parameters and variable binding.

Basic pattern matching

The most basic pattern matching in Swift is the switch statement.

let number = 5

switch number {
case 1:
    print("The number is 1")
case 2:
    print("The number is 2")
case 3, 4:
    print("The number is 3 or 4")
default:
    print("The number is not 1, 2, 3, or 4")
}

Matching ranges

One of the most useful features of pattern matching in Swift is the ability to match ranges of values.

let age = 25

switch age {
case 0..<18:
    print("You are not an adult")
case 18..<21:
    print("You are a young adult")
case 21..<30:
    print("You are in your 20s")
default:
    print("You are 30 or older")
}

Matching optionals

let optionalName: String? = "John"

if let name = optionalName {
    print("Hello, \(name)!")
} else {
    print("Hello, stranger.")
}

You can also use the if casesyntax to match additional conditions in a pattern:

let name: String? = "John"

if case .some(let value) = name, value == "John" {
    print("The name is John")
}

Matching tuples

let coordinates = (x: 10, y: 20)

switch coordinates {
case (0, 0):
    print("The coordinates are at the origin")
case (_, 0):
    print("The coordinates are on the x-axis")
case (0, _):
    print("The coordinates are on the y-axis")
case (let x, let y) where x == y:
    print("The coordinates are on the line y = x")
default:
    print("The coordinates")
}

You can use a wildcard pattern (_) to match any value in a tuple.

let tuple = (42, "hello", true)

switch tuple {
case (_, _, true):
    print("The third value is true")
default:
    print("The third value is false or not a Bool")
}

Matching enums

enum Result {
    case success(Int)
    case failure(Error)
}

let result = Result.success(42)

switch result {
case .success(let value):
    print("The result is a success with value \(value)")
case .failure(let error):
    print("The result is a failure with error \(error)")
}

Matching with labels

let person = (name: "John", age: 25)

switch person {
case ("John", _):
    print("Hello, John!")
case (_, let age) where age < 18:
    print("You're too young, sorry.")
case (_, let age) where age >= 18:
    print("Welcome!")
default:
    break
}

Expression patterns

Expression patterns are used to match a value against a specific expression. The expression can be a constant, variable, function call, or any other valid expression in Swift.

let value = 42

switch value {
case 0...10:
    print("The value is between 0 and 10")
case 11...20:
    print("The value is between 11 and 20")
case 21...30:
    print("The value is between 21 and 30")
case let x where x % 2 == 0:
    print("The value is an even number")
default:
    print("The value is something else")
}

// Output: The value is an even number

The fourth case uses an expression pattern with a where clause to match any even number. The pattern matches if the value is divisible by 2 with no remainder.

Wildcard patterns

The wildcard pattern is used to match any value without binding it to a variable or constant. It’s denoted by an underscore (_) character, which acts as a placeholder for any value.

let value = "hello"

switch value {
case "goodbye":
    print("The value is 'goodbye'")
case _:
    print("The value is something else")
}

The second pattern uses a wildcard pattern to match any other value. Since the wildcard pattern doesn’t bind the value to a variable or constant, we can’t use the matched value in the print statement.

Custom expression matching behavior

You can overload the ~= operator to provide custom expression matching behavior.

// Overload the ~= operator to match a string with an integer.
func ~= (pattern: String, value: Int) -> Bool {
    return pattern == "\(value)"
}

let point = (1, 2)

switch point {
case ("0", "0"):
    print("(0, 0) is at the origin.")
default:
    print("The point is at (\(point.0), \(point.1)).")
}
// Prints "The point is at (1, 2)."