← thecodex.expert · The Codex Family of Knowledge
Swift

Closures

Try to store a closure for later without marking its parameter @escaping, and Swift simply won’t compile it — a real, enforced distinction most languages leave entirely to convention.

Swift 6 @escaping is compiler-enforced Last verified:
Canonical Definition

{ (params) -> ReturnType in body } defines a closure; when it's the last argument, trailing closure syntax lets it move outside the parentheses (or replace them entirely for a single-closure call), and $0/$1 provide implicit shorthand names for a closure's parameters when they're not named explicitly. Closures capture variables from their enclosing scope BY REFERENCE, not by copying — a captured var's later mutations are visible inside the closure. By default, a closure parameter is assumed non-escaping (used only during the function call itself); marking it @escaping is REQUIRED, and compiler-enforced, the moment the closure might be stored and called after the function has already returned — for instance, as a completion handler for an asynchronous operation.

Trailing closures and shorthand argument names

$0 and $1 refer to the closure's first and second parameters implicitly — no need to write ( a, b ) in explicitly for a short, obvious closure body.

Swifttrailing_closures.swift
let numbers = [5, 2, 8, 1]

let sorted1 = numbers.sorted(by: { (a, b) -> Bool in a > b })   // full syntax
let sorted2 = numbers.sorted { $0 > $1 }                             // trailing + shorthand

print(sorted2)   // [8, 5, 2, 1]

Capturing by reference: a live connection, not a snapshot

increment holds a genuine reference to total, not a frozen copy of its value at creation time — each call sees and updates the SAME captured variable, which is exactly what makes this pattern work as a counter.

Swiftcapture_by_reference.swift
func makeCounter() -> () -> Int {
    var total = 0
    let increment = {
        total += 1   // captures 'total' by reference
        return total
    }
    return increment
}

let counter = makeCounter()
print(counter())   // 1
print(counter())   // 2 — 'total' genuinely persisted between calls

@escaping: required when a closure outlives the call

Without @escaping, this wouldn't compile at all — storing completion for later use, past the point where fetchData itself has already returned, is exactly the scenario @escaping exists to permit and make explicit.

Swiftescaping_closure.swift
var savedCompletion: (() -> Void)?

func fetchData(completion: @escaping () -> Void) {   // REQUIRED — stored for later, outliving this call
    savedCompletion = completion   // this line is exactly why @escaping is required
}

Sources

1
Apple / Swift.org. The Swift Programming Language, "Closures," docs.swift.org/swift-book.