All posts
4 min read

@autoclosure && @escape

On this page

Closures are first-class citizens in Swift, which means you can pass them around as arguments. While learning Swift, you keep running into keywords that decorate these closure parameters — @autoclosure and @escape being two of the most common.

@escape and @nonescape

When a closure is passed as an argument to a function but only gets executed after that function has already returned, we say the closure escapes the function. The @escape keyword marks that the closure is allowed to be called after the function returns.

Here’s the example from the official Swift docs:

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

The argument to someFunctionWithEscapingClosure(_:) is a closure, and inside the function we stash that closure into an array so we can call it later. Notice the @escaping keyword on the parameter declaration — without it the compiler will yell at you:

error: passing non-escaping parameter 'completionHandler' to function expecting an @escaping closure
    completionHandlers.append(completionHandler)    

One consequence of @escaping is that you must refer to self explicitly inside the closure. The docs include another example to illustrate:

func someFunctionWithNonescapingClosure(closure: () -> Void) {
    closure()
}
 
class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNonescapingClosure { x = 200 }
    }
}
 
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
 
completionHandlers.first?()
print(instance.x)
// Prints "100”

someFunctionWithEscapingClosure(_:) is an escaping closure, so you have to reference self explicitly. someFunctionWithNonescapingClosure(_:) is non-escaping, so you can leave self implicit.

@autoclosure

Walking through an example

Let’s see what @autoclosure actually does with a small example.

Consider this function f, which takes a single argument of type () -> Bool:

func f(predicate: () -> Bool) {
    if predicate() {
        print("It's true")
    }
}

You can call it by passing a closure of the right type:

f(predicate: {2 > 1})
// "It's true"

But if you drop the { and } around the closure, it won’t compile:

f(predicate: 2 > 1)
// error: '>' produces 'Bool', not the expected contextual result type '() -> Bool'

As the docs put it: an autoclosure is a closure that gets wrapped around an expression you pass as an argument to a function. The closure takes no arguments, and when it’s called it returns the value of the expression it wraps. Swift offers this syntactic sugar so you can drop the {} and just write the expression directly.

Going back to the example: if you write something like 2 > 1 and pass it to f, the expression is automatically wrapped, becoming { 2 > 1 } before being handed to f.

func f(predicate: @autoclosure () -> Bool) {
    if predicate() {
        print("It's true")
    }
}

f(predicate: 2 > 1)
// It's true

⚠️ @autoclosure doesn’t support parameters that take inputs — only () -> T signatures can be simplified this way.

Delay evaluation

Swift’s ?? operator looks like this:

let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)

If the Optional has a value, return it; otherwise return the default value on the right-hand side. If you look at the actual implementation of ??, you’ll see:

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T?) -> T?

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T

So ?? is a binary operator — optional is the left-hand side and defaultValue is the right-hand side. You might wonder: in our example above, fullName is just a String, so how does it become () -> T? That’s @autoclosure doing its work. As we just covered, the keyword wraps the expression into a closure that returns the expression’s value. What actually gets passed as the second argument is { fullName }.

So we can guess the implementation looks roughly like this (since fullName is String, the second overload is the one selected):

func ??<T>(optional: T?, defaultValue: @autoclosure () -> T) -> T {
    switch optional {
        case .Some(let value):
            return value
        case .None:
            return defaultValue()
        }
}

One more thing worth noting: an expression decorated with @autoclosure evaluates lazily. That is, the expression inside the closure isn’t evaluated until the closure is actually called, which avoids unnecessary work — especially valuable when the default value is the result of an expensive computation.