Skip to content

9. Functions

What & why

Functions package reusable logic behind a name. Swift's functions are unusually expressive: arguments carry labels so call sites read like sentences (move(from: a, to: b)), and functions are first-class values you can pass around.

Defining and calling

func greet(name: String) -> String {
    return "Hello, \(name)!"
}

print(greet(name: "Ada"))     // Hello, Ada!
  • func name(parameters) -> ReturnType { ... }
  • A function with no -> returns Void (nothing).
  • If the body is a single expression, return is optional:
func square(_ x: Int) -> Int { x * x }

Argument labels

Each parameter has an argument label (used by the caller) and a parameter name (used inside):

func move(from start: Int, to end: Int) {
    print("moving from \(start) to \(end)")   // inside: start, end
}
move(from: 3, to: 9)                          // outside: from, to

Write _ as the label to drop it:

func add(_ a: Int, _ b: Int) -> Int { a + b }
add(2, 3)

If you write just one name, it serves as both label and parameter name (greet(name:) above). Swift's API Design Guidelines favour labels that make a call read naturally: insert(x, at: 0), remove(at: 2), contains(where:).

Default values

func connect(host: String, port: Int = 443, secure: Bool = true) {
    print("\(secure ? "https" : "http")://\(host):\(port)")
}

connect(host: "example.com")                       // https://example.com:443
connect(host: "localhost", port: 8080, secure: false)
connect(host: "example.com", secure: false)        // skip port, keep its default

Returning multiple values: tuples

func minMax(of numbers: [Int]) -> (min: Int, max: Int)? {
    guard var lo = numbers.first else { return nil }
    var hi = lo
    for n in numbers.dropFirst() {
        lo = min(lo, n)
        hi = max(hi, n)
    }
    return (lo, hi)
}

if let result = minMax(of: [4, 9, 1, 7]) {
    print(result.min, result.max)   // 1 9
}

Variadic parameters

func average(_ numbers: Double...) -> Double {
    numbers.isEmpty ? 0 : numbers.reduce(0, +) / Double(numbers.count)
}
print(average(2, 4, 9))     // 5.0

Inside the function, numbers is an [Double].

Parameters are constants — inout for changing them

func double(_ x: inout Int) {
    x *= 2
}

var n = 21
double(&n)          // the & marks the argument as mutable
print(n)            // 42

Use inout sparingly; returning a new value is usually clearer. The standard library uses it for things like swap(&a, &b).

Functions as values

A function has a type, written (ParamTypes) -> ReturnType:

func triple(_ x: Int) -> Int { x * 3 }

let f: (Int) -> Int = triple
print(f(4))                       // 12
print([1, 2, 3].map(triple))      // [3, 6, 9]

func makeAdder(_ amount: Int) -> (Int) -> Int {
    return { value in value + amount }      // a closure capturing `amount`
}
let addTen = makeAdder(10)
print(addTen(5))                  // 15

Closures (the { value in ... } syntax) get their own lesson in Intermediate.

Nested functions

Functions can live inside functions, which is great for private helpers:

func process(_ values: [Int]) -> Int {
    func isValid(_ v: Int) -> Bool { v > 0 }
    return values.filter(isValid).count
}

Recursion

func factorial(_ n: Int) -> Int {
    n <= 1 ? 1 : n * factorial(n - 1)
}
print(factorial(5))   // 120

Every recursive function needs a base case. Swift does not guarantee tail-call optimization; very deep recursion (tens of thousands of frames) can overflow the stack — convert to a loop when depth is unbounded.

Overloading

Functions can share a name if their parameter types, labels, or count differ:

func describe(_ x: Int) -> String { "int \(x)" }
func describe(_ x: String) -> String { "string \(x)" }
func describe(_ x: Int, times: Int) -> String { String(repeating: "\(x) ", count: times) }

The return type alone isn't enough to distinguish overloads in most cases.

Documenting functions

/// Returns the average of `values`, or `nil` when the array is empty.
///
/// - Parameter values: The numbers to average.
/// - Returns: The arithmetic mean.
func mean(_ values: [Double]) -> Double? {
    guard !values.isEmpty else { return nil }
    return values.reduce(0, +) / Double(values.count)
}

Option-click a function name in Xcode, or run DocC, to see these rendered.

@discardableResult

If callers are allowed to ignore a return value, say so — otherwise the compiler warns:

@discardableResult
func logAndReturn(_ message: String) -> String {
    print(message)
    return message
}
logAndReturn("hi")      // no "unused result" warning

Common mistakes

  • Forgetting argument labels at the call site (greet("Ada") instead of greet(name: "Ada")).
  • Trying to modify a parameter — parameters are constants; copy into a var or use inout.
  • Forgetting & when passing to an inout parameter.
  • Missing base case in recursion.
  • Overusing inout and tuples where a small struct would be clearer.
  • Ignoring return values accidentally — the warning is there for a reason.

Exercises

  1. Write func isEven(_ n: Int) -> Bool and use it with filter on 1...20.
  2. Write func fullName(first: String, middle: String? = nil, last: String) -> String.
  3. Write func stats(of numbers: [Double]) -> (mean: Double, min: Double, max: Double)?.
  4. Write swapValues for two Ints with inout, then check how swap(&a, &b) compares.
  5. Implement fibonacci(_ n: Int) -> Int recursively, then iteratively. Time both for n = 35.
  6. Write func makeCounter() -> () -> Int that returns a function that returns 1, 2, 3… on successive calls.
  7. Overload describe to handle Int, Double, [Int].

Recap

  • func name(label param: Type = default) -> Result; single-expression bodies skip return.
  • Argument labels make call sites read like prose; _ removes a label.
  • Return tuples for several values; Type... for variadics; inout + & to mutate in place.
  • Functions are values with types like (Int) -> Int.
  • Recursion needs a base case; overloads are resolved by labels and types.

Next: Optionals →