Lesson 46Closures

Autoclosures

What is @autoclosure?

The @autoclosure attribute automatically wraps an expression in a closure. This delays evaluation and makes the syntax cleaner!

Comparison

Without @autoclosure

check({ 5 > 3 })

Must use braces

With @autoclosure

check(5 > 3)

Clean expression syntax

Key Benefits

  • Cleaner syntax - No braces needed
  • Delayed evaluation - Computed only when called
  • Short-circuit - Skip expensive operations

When to Use

  • Assert/precondition functions
  • Default value providers
  • Conditional logging
  • Short-circuit evaluation

Combining with @escaping

func store(_ check: @autoclosure @escaping () -> Bool)

Use both when you need to store the autoclosure for later execution.

main.swift
// AUTOCLOSURES
// Automatically wraps an expression in a closure
// Delays evaluation until called

// WITHOUT @autoclosure
func logIfTrue(_ condition: () -> Bool, _ message: String) {
    if condition() {
        print(message)
    }
}

// Must pass a closure with braces
logIfTrue({ 5 > 3 }, "Five is greater!")

// WITH @autoclosure
func logIfTrueAuto(_ condition: @autoclosure () -> Bool, _ message: String) {
    if condition() {
        print(message)
    }
}

// Can pass expression directly - cleaner!
logIfTrueAuto(5 > 3, "Five is greater!")

// DELAYED EVALUATION
var counter = 0

func incrementAndCheck() -> Bool {
    counter += 1
    print("Counter is now: \(counter)")
    return counter > 5
}

// Without autoclosure - called immediately
func regularCheck(_ check: Bool) {
    print("Regular: \(check)")
}

// With autoclosure - called only when needed
func autoCheck(_ check: @autoclosure () -> Bool) {
    print("About to check...")
    print("Auto: \(check())")  // NOW it's called
}

// PRACTICAL EXAMPLE: Assert-like function
func myAssert(_ condition: @autoclosure () -> Bool,
              _ message: @autoclosure () -> String) {
    #if DEBUG
    if !condition() {
        print("Assertion failed: \(message())")
    }
    #endif
}

myAssert(1 + 1 == 2, "Math is broken!")

// COMBINING @autoclosure AND @escaping
var pendingChecks: [() -> Bool] = []

func addPendingCheck(_ check: @autoclosure @escaping () -> Bool) {
    pendingChecks.append(check)
}

addPendingCheck(counter > 10)
addPendingCheck(counter < 100)

// Run checks later
for check in pendingChecks {
    print("Check result: \(check())")
}

// SHORT-CIRCUIT EVALUATION (like || and &&)
func orElse(_ a: Bool, _ b: @autoclosure () -> Bool) -> Bool {
    if a { return true }
    return b()  // Only evaluated if a is false
}

let result = orElse(true, expensiveOperation())
// expensiveOperation() is NEVER called!

// REAL-WORLD: Nil-coalescing alternative
func unwrap<T>(_ optional: T?, default defaultValue: @autoclosure () -> T) -> T {
    if let value = optional {
        return value
    }
    return defaultValue()  // Only computed if needed
}

let name: String? = nil
let greeting = unwrap(name, default: "Guest")
print(greeting)  // Guest

Try It Yourself!

Create a function debug(_ message: @autoclosure () -> String) that only prints in debug mode!