Polyglot
Languages Swift functions
Swift § functions

Functions and closures

Swift functions are first-class values with substantial signature flexibility: parameter labels, default values, variadic parameters, in-out parameters, throwing (and async) modifiers, and generic type parameters. Closures are anonymous functions — substantial in idiomatic Swift code (many APIs accept closures as arguments). The conventional Swift closure surface admits trailing closure syntax, shorthand parameter names ($0, $1), capture lists for explicit reference behaviour, and @escaping / @autoclosure attributes. The combination — labelled parameters, the substantial closure surface, the trailing-closure form, the type-system integration — is the substance of Swift’s function surface.

Function declarations

The principal form:

func name(parameters) -> ReturnType {
    body
}

Examples:

func add(_ a: Int, _ b: Int) -> Int {
    a + b                                          // implicit return for single-expression
}

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

func performAction() {                             // no return value (Void / ())
    print("acting")
}

The _ (wildcard) suppresses the parameter label at the call site:

func add(_ a: Int, _ b: Int) -> Int { a + b }
add(3, 4)                                          // no labels needed

func send(message: String, to recipient: String) {
    /* ... */
}
send(message: "hi", to: "Alice")                   // labels visible

Parameter labels

Swift admits distinct argument labels and parameter names:

func send(message msg: String, to recipient: String) {
    print("\(msg)\(recipient)")
}

send(message: "hi", to: "Alice")                   // call site sees labels

The label is what the caller writes; the parameter name is what the implementation uses.

When the same name is acceptable, only one is needed:

func send(message: String, to recipient: String) {
    print("\(message)\(recipient)")             // message is both
}

The conventional API design uses substantive labels:

func insert(_ item: Item, at index: Int)
//          ^                ^
//          no label         "at" label

source.insert(item, at: 0)

Default arguments

func greet(name: String = "world", greeting: String = "Hello") {
    print("\(greeting), \(name)")
}

greet()                                            // "Hello, world"
greet(name: "Alice")                               // "Hello, Alice"
greet(name: "Alice", greeting: "Hi")

Default values are admitted; the conventional discipline uses defaults freely.

Variadic parameters

The ... admits any number of arguments:

func sum(_ nums: Int...) -> Int {
    nums.reduce(0, +)
}

sum()                                              // 0
sum(1, 2, 3)                                       // 6

Inside the function, nums is [Int]. For passing an array:

let arr = [1, 2, 3]
sum(arr)                                           // ERROR: arr is [Int], expected Int...

Pre-Swift 6, no spread-into-variadic; conventional alternatives:

// Define a non-variadic alternative:
func sum(_ nums: [Int]) -> Int {
    nums.reduce(0, +)
}

// Or use the array directly inside the variadic version:
func sum(_ nums: Int...) -> Int { sum(nums) }      // delegate to array form

In-out parameters

The inout admits modifying the caller’s value:

func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

var x = 1
var y = 2
swap(&x, &y)                                       // & at call site
print(x, y)                                        // 2 1

The mechanism admits substantial efficiency for substantial structs without copying.

The standard library’s swap(_:_:) is the conventional form.

Throwing functions

Functions that may throw errors:

enum ParseError: Error {
    case empty
    case invalid(reason: String)
}

func parse(_ input: String) throws -> Int {
    guard !input.isEmpty else { throw ParseError.empty }
    guard let n = Int(input) else { throw ParseError.invalid(reason: "not numeric") }
    return n
}

do {
    let n = try parse("42")
    print(n)
} catch ParseError.empty {
    print("empty")
} catch ParseError.invalid(let reason) {
    print("invalid: \(reason)")
} catch {
    print("other: \(error)")
}

Treated in Error handling.

Async functions

Functions that suspend execution:

func fetchData(from url: URL) async throws -> Data {
    let (data, _) = try await URLSession.shared.data(from: url)
    return data
}

let data = try await fetchData(from: url)

Treated in Concurrency.

Generic functions

Type parameters in angle brackets:

func first<T>(in array: [T]) -> T? {
    array.first
}

func max<T: Comparable>(_ a: T, _ b: T) -> T {
    a > b ? a : b
}

Treated in Generics.

Closures

Anonymous functions:

let add: (Int, Int) -> Int = { a, b in
    a + b
}

let result = add(3, 4)                             // 7

The form: { parameters in body }. The closure type is (In) -> Out.

Examples in idiomatic use:

[1, 2, 3].map { x in x * 2 }                       // explicit name
[1, 2, 3].map { $0 * 2 }                           // shorthand
[1, 2, 3].sorted { a, b in a > b }
[1, 2, 3].filter { $0 > 1 }
[1, 2, 3].reduce(0, +)                             // function reference

The shorthand parameter names $0, $1, $2 admit substantial conciseness.

Trailing closures

The trailing closure syntax — admit closures outside the parentheses if they are the last argument:

[1, 2, 3].map { $0 * 2 }                           // trailing closure

UIView.animate(withDuration: 0.5) {
    view.alpha = 0
} completion: { _ in
    print("done")
}                                                  // multiple trailing closures (Swift 5.3+)

The mechanism admits substantial DSL-like syntax — conventional in SwiftUI, Combine, etc.

Capture lists

Closures may capture variables from the enclosing scope; the capture list admits explicit capture behaviour:

class ViewController {
    var count = 0

    func setupHandler() {
        button.action = { [weak self] in
            self?.count += 1
        }
    }
}

The capture list [weak self] admits weak capture — avoiding retain cycles.

For value-capture:

let n = 42
let f = { [n] in print(n) }                       // captures n by value

Treated in Memory and ARC.

@escaping closures

Closures that outlive the calling function require the @escaping attribute:

class Server {
    private var handlers: [(Request) -> Response] = []

    func register(handler: @escaping (Request) -> Response) {
        handlers.append(handler)
    }
}

Without @escaping, the closure must run during the function call. The mechanism admits substantial type-safety around closure lifetimes.

@autoclosure

@autoclosure admits passing an expression that is wrapped into a closure automatically:

func evaluate(condition: () -> Bool) {
    if condition() { print("true") }
}

evaluate { x > 0 }                                 // explicit closure

func evaluate(condition: @autoclosure () -> Bool) {
    if condition() { print("true") }
}

evaluate(x > 0)                                    // expression auto-wrapped

The mechanism admits substantial DSL-style syntax. The standard library’s assert(_:_:) and ?? use @autoclosure for the right-hand operand.

Function types

Functions are first-class values; their type is (In) -> Out:

let add: (Int, Int) -> Int = { $0 + $1 }
let greet: (String) -> Void = { print("Hello, \($0)") }
let supplier: () -> Int = { 42 }

// Function-typed parameters:
func apply(_ f: (Int) -> Int, to x: Int) -> Int {
    f(x)
}

apply({ $0 * 2 }, to: 5)                           // 10

// Returning a function:
func makeAdder(_ n: Int) -> (Int) -> Int {
    return { $0 + n }
}

let add5 = makeAdder(5)
add5(3)                                            // 8

Higher-order functions

The standard library admits substantial higher-order functions:

arr.map { $0 * 2 }
arr.filter { $0 > 0 }
arr.reduce(0, +)
arr.compactMap { Int($0) }
arr.flatMap { [$0, $0 * 2] }
arr.sorted(by: <)
arr.first(where: { $0.isActive })
arr.allSatisfy(\.isValid)                          // key path syntax

Method references and key paths

Function references admit substantial conciseness:

[1, 2, 3].map(String.init)                         // ["1", "2", "3"]
strings.compactMap { Int($0) }                     // explicit closure
strings.compactMap(Int.init)                       // function reference

// Key paths:
people.sorted { $0.age < $1.age }                  // explicit
people.sorted(by: { $0.age < $1.age })
people.sorted(using: KeyPathComparator(\.age))     // key-path comparator

The \.path syntax admits key paths — first-class references to property paths:

let nameKeyPath = \Person.name
let person = Person(name: "Alice", age: 30)
let name = person[keyPath: nameKeyPath]            // "Alice"

Common patterns

Default + named labels

func fetch(url: URL,
           method: String = "GET",
           headers: [String: String] = [:],
           timeout: TimeInterval = 30) async throws -> Data {
    /* ... */
}

let data = try await fetch(url: url)
let data = try await fetch(url: url, method: "POST")
let data = try await fetch(url: url, headers: ["Authorization": token], timeout: 10)

Higher-order function

func retry<T>(attempts: Int = 3, _ operation: () throws -> T) throws -> T {
    var lastError: Error?
    for _ in 0..<attempts {
        do {
            return try operation()
        } catch {
            lastError = error
        }
    }
    throw lastError!
}

let result = try retry(attempts: 5) {
    try fetchData()
}

Builder pattern via methods

struct URLBuilder {
    private var components = URLComponents()

    func scheme(_ s: String) -> URLBuilder {
        var b = self
        b.components.scheme = s
        return b
    }

    func host(_ h: String) -> URLBuilder {
        var b = self
        b.components.host = h
        return b
    }

    func path(_ p: String) -> URLBuilder {
        var b = self
        b.components.path = p
        return b
    }

    func build() -> URL? {
        components.url
    }
}

let url = URLBuilder()
    .scheme("https")
    .host("example.com")
    .path("/api/users")
    .build()

Throwing closure with retry

func with<T>(retries: Int, base delay: TimeInterval = 1.0,
              _ operation: () async throws -> T) async throws -> T {
    var lastError: Error?
    for attempt in 0..<retries {
        do {
            return try await operation()
        } catch {
            lastError = error
            if attempt < retries - 1 {
                try await Task.sleep(for: .seconds(delay * pow(2.0, Double(attempt))))
            }
        }
    }
    throw lastError!
}

Function as parameter type

typealias Predicate<T> = (T) -> Bool

extension Sequence {
    func count(where predicate: Predicate<Element>) -> Int {
        reduce(0) { count, element in
            count + (predicate(element) ? 1 : 0)
        }
    }
}

[1, 2, 3, 4, 5].count(where: { $0.isMultiple(of: 2) })  // 2

Currying via closures

func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
    return { a in { b in f(a, b) } }
}

let add: (Int, Int) -> Int = (+)
let add5 = curry(add)(5)
print(add5(3))                                     // 8

Capture list with multiple variants

class Service {
    var counter = 0
    weak var delegate: ServiceDelegate?

    func setup() {
        completion = { [weak self, weak delegate] in
            self?.counter += 1
            delegate?.didComplete()
        }
    }
}

Method reference in higher-order function

let numbers = ["1", "two", "3", "four", "5"]
let parsed = numbers.compactMap(Int.init)          // [1, 3, 5]

let people = [...]
let names = people.map(\.name)                     // key path
let sorted = people.sorted(by: { $0.age < $1.age })

Returning a closure

func makeCounter() -> () -> Int {
    var count = 0
    return {
        count += 1
        return count
    }
}

let counter = makeCounter()
print(counter())                                   // 1
print(counter())                                   // 2

The pattern admits substantial state encapsulation.

@autoclosure for lazy evaluation

func cache<T>(_ key: String, compute: @autoclosure () -> T) -> T {
    if let cached = lookup(key) as? T { return cached }
    let value = compute()
    store(key, value)
    return value
}

let result = cache("expensive") { computeExpensive() }
let result = cache("simple", compute: 42)          // expression auto-wrapped

Result builder for DSL

@resultBuilder
struct ListBuilder {
    static func buildBlock(_ items: String...) -> [String] {
        Array(items)
    }
}

func makeList(@ListBuilder _ builder: () -> [String]) -> [String] {
    builder()
}

let list = makeList {
    "first"
    "second"
    "third"
}                                                   // ["first", "second", "third"]

Treated in Property wrappers.

inout for modifying

func increment(_ value: inout Int, by delta: Int = 1) {
    value += delta
}

var count = 0
increment(&count)                                  // 1
increment(&count, by: 10)                          // 11

Generic function with constraints

func min<T: Comparable>(_ values: T...) -> T? {
    values.min()
}

let m = min(3, 1, 4, 1, 5, 9)                      // Optional(1)
let s = min("banana", "apple", "cherry")           // Optional("apple")

Variadic with closure

func combineAll<T, R>(
    _ values: T...,
    using combine: (T, T) -> R
) -> [R] {
    var result: [R] = []
    for i in 0..<(values.count - 1) {
        result.append(combine(values[i], values[i+1]))
    }
    return result
}

A note on Void and ()

The Void type is an alias for the empty tuple ():

typealias Void = ()

func performAction() {                             // returns Void / ()
    print("done")
}

func performAction() -> Void { /* ... */ }         // explicit
func performAction() -> () { /* ... */ }           // equivalent

The conventional discipline omits the explicit Void return type.

A note on the conventional discipline

The contemporary Swift functions advice:

  • Use parameter labels generously — admit self-documenting calls.
  • Use _ for unlabelled parameters (e.g., binary operators on values).
  • Use defaults freely.
  • Use variadics for genuinely variadic functions; arrays for the common case.
  • Use inout sparingly — for explicit in-place modification.
  • Use throws for errors.
  • Use async for asynchrony.
  • Use generics for genuinely generic code.
  • Use trailing closures for callbacks and DSLs.
  • Use shorthand parameter names ($0, $1) for short closures.
  • Use capture lists ([weak self]) in closures stored on instances.
  • Use key paths (\.path) for property references.
  • Use @autoclosure sparingly — for substantial DSL benefit.

The combination — labelled parameters, defaults, variadics, in-out, generics, the substantial closure surface (trailing, capture lists, autoclosure), the function-as-value mechanism, key paths — is the substance of Swift’s function surface. The discipline produces clear, type-safe, expressive function code with substantial flexibility for substantial functional patterns.