Using @autoclosure when designing Swift APIs

You deserve to read this post with syntax highlighting and in a much nicer format! 😉 Click above to read this post on my blog instead 👆

Swift’s @autoclosure attribute enables you to define an argument that automatically gets wrapped in a closure. It’s primarily used to defer execution of a (potentially expensive) expression to when it’s actually needed, rather than doing it directly when the argument is passed.

One example of when this is used in the Swift standard library is the assert function. Since asserts are only triggered in debug builds, there’s no need to evaluate the expression that is being asserted in a release build. This is where @autoclosure comes in:

func assert(_ expression: @autoclosure () -> Bool,
_ message: @autoclosure () -> String) {
guard isDebug else {
return
}

// Inside assert we can refer to expression as a normal closure
if !expression() {
assertionFailure(message())
}
}

I’m paraphrasing the implementation of assert a bit above, the actual implementation can be found here.

The nice thing about @autoclosure is that it has no effect on the call site. If assert was implemented using “normal” closures you’d have to use it like this:

assert({ someCondition() }, { "Hey, it failed!" })

But now, you can just call it like you would any function that takes non-closure arguments:

assert(someCondition(), "Hey it failed!")

This week, let’s take a look at how we can use @autoclosure in our own code, and how it enables us to design some pretty nice APIs.

Inlining assignments

One thing that @autoclosure enables is to inline expressions in a function call. This enables us to do things like passing assignment expressions as an argument. Let’s take a look at an example where this can be useful.

On iOS, you normally define view animations using this API:

UIView.animate(withDuration: 0.25) {
view.frame.origin.y = 100
}

With @autoclosure, we could write an animate function that automatically creates an animation closure and executes it, like this:

func animate(_ animation: @autoclosure(escaping) () -> Void,
duration: TimeInterval = 0.25) {
UIView.animate(withDuration: duration, animations: animation)
}

Now, we can simply perform our animation with a simple function call without any extra {} syntax:

animate(view.frame.origin.y = 100)

Using the above technique, we can really reduce the verbosity of our animation code, without sacrificing readability or expressiveness 🎉

Passing errors as expressions

Another situation that I find @autoclosure very useful in is when writing utilities that deal with errors. For example, let’s say we want to add an extension on Optional that enables us to unwrap it using a throwing API. That way we can require the optional to be non-nil, or else throw an error, like this:

extension Optional {
func unwrapOrThrow(_ errorExpression: @autoclosure () -> Error) throws -> Wrapped {
guard let value = self else {
throw errorExpression()
}
        return value
}
}

Similar to how assert is implemented, we only evaluate the error expression when needed, rather than having to do it for every time we attempt to unwrap an optional. We can now use our unwrapOrThrowAPI like this:

let name = try argument(at: 1).unwrapOrThrow(ArgumentError.missingName)

Type inference using default values

The final use case for @autoclosure that I’ve found is when extracting an optional value from a dictionary, a database, or UserDefaults.

Normally, when extracting a value from an untyped dictionary and providing a default value, you’d have to write something like this:

let coins = (dictionary["numberOfCoins"] as? Int) ?? 100

That’s kind of hard to read, and has a lot of syntax cruft with the casting and the ?? operator. With @autoclosure, we can define an API that enables us to write the same expression like this instead:

let coins = dictionary.value(forKey: "numberOfCoins", defaultValue: 100)

Above, we can see that the default value is both used for when a value was missing, but also enables Swift to do type inference on the value, without us having to specify the type or perform casting. Pretty neat 👍

Let’s take a look at how we would write such an API:

extension Dictionary where Value == Any {
func value<T>(forKey key: Key, defaultValue: @autoclosure () -> T) -> T {
guard let value = self[key] as? T else {
return defaultValue()
}
        return value
}
}

Again, we use @autoclosure to avoid having to evaluate the default value every time this method is called.

Conclusion

Reducing verbosity is always something that needs to be done with careful consideration. Our goal should always be to write expressive, easy to read code, so we need to make sure that we don’t remove important information from the call site when designing low verbosity APIs.

I think when used in appropriate situations, @autoclosure is a great tool for doing just that. Dealing with expressions, instead of just values, enables us to reduce verbosity and cruft, while also potentially gaining better performance.

Do you have some other uses of @autoclosure that you think are really useful? Let me know, along with any other comments, questions or feedback that you might have, on Twitter @johnsundell.

Thanks for reading! 🚀

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.