What is @autoclosure? When is it justified?
Predict first
How many times does expensive() run?
func expensive() -> String { print("work"); return "x" }
func log(_ m: @autoclosure () -> String, enabled: Bool) {
if enabled { print(m()) }
}
log(expensive(), enabled: false)
- 0
- 1
Mental model. The compiler wraps your argument in a closure for you — so the expression is not evaluated until someone actually calls it.
@autoclosure turns an expression argument into a closure automatically. It is why assert(condition) does not evaluate the message on the success path, and why ?? can avoid work for the default value.
It is justified for delayed evaluation of an argument that is usually not needed. It is dangerous when the closure is @escaping (captures the expression and its context), or when callers cannot tell that work is deferred.
@autoclosure wraps the argument in a closure. It is only evaluated if the function calls it, which here it never does.
Common traps
- Using it to look clever in app APIs. Callers lose the ability to see evaluation order, and a side-effecting expression can run zero times or twice.
- Combining
@autoclosurewith@escapingaccidentally. The expression's captures now live as long as the closure — a hidden retain.
They might follow up with
- Give a stdlib example.
??,&&,||, andassertall take autoclosures. - How do you make it escaping?
@autoclosure @escaping— and then document it loudly.
Related questions
- 1.5.1 Escaping vs non-escaping closures?
- 1.5.8 What is a result builder (@ViewBuilder, @CommandsBuilder)?
← unowned var view = UIView() then print(view) — what happens? What is a result builder (@ViewBuilder, @CommandsBuilder)? →