How to use withoutActuallyEscaping


Greetings, traveler!

Closures are potent tools in the Swift language. They can be escaping or non-escaping. By default, all closures are non-escaping. Optional closures are implicitly escaping since an Optional enum wraps them and internally saves the value.

There are some cases where we don’t need the closure to be an escaping one. Check out this example. We will try to create something like the allSatisfy(_:) method for collections.

extension Collection {    
    func allElementsSatisfy(_ predicate: (Element) -> Bool) -> Bool {
        self.lazy.filter { !predicate($0) }.isEmpty // ❌ Error
    }
}

The lazy keyword prevents the filter method from continuing to execute after finding the first value that does not match our predicate. Since the lazy collection does not persist, its filter method only expects escaping closures. So we will get an error:

Escaping closure captures non-escaping parameter ‘predicate’

To fix this issue, we can use withoutActuallyEscaping(_:do:). Check out this example.

extension Collection {
    func allElementsSatisfy(_ predicate: (Element) -> Bool) -> Bool {
        withoutActuallyEscaping(predicate) { escapablePredicateCopy in
            self.lazy.filter { !escapablePredicateCopy($0) }.isEmpty
        }
    }
}

withoutActuallyEscaping(_:do:) created a temporarily escapable copy of our predicate closure, so now we can pass it through the filter method parameters. This copy will be only valid inside the withoutActuallyEscaping(_:do:) scope.

Now, we can use it like this:

struct User {
    var isAdmin: Bool
}

let users = [
    User(isAdmin: false),
    User(isAdmin: true),
    User(isAdmin: true)
]

let isAllAdmins = array.allElementsSatisfy { $0.isAdmin }

print(isAllAdmins) // false

Conclusion

This is a pretty rare but interesting case where we can use the Swift API in the way it is meant to be used.