In what order are operands, default arguments, autoclosures and assert conditions evaluated?
Predict first
What does this print (debug build)?
func t(_ s: String, _ r: Bool = true) -> Bool { print(s, terminator: " "); return r }
func v(_ s: String, _ n: Int) -> Int { print(s, terminator: " "); return n }
_ = t("a", false) && t("b")
_ = t("c") || t("d")
_ = t("e", false) || t("f", true) && t("g")
print()
let x: Int? = 5
print(x ?? v("dflt", 0))
print(Optional<Int>.none ?? v("dflt2", 0))
print(v("l", 1) + v("m", 2) * v("n", 3))
var counter = 0
func next() -> Int { counter += 1; return counter }
func show(_ d: Int = next()) { print("d =", d) }
show(); show(); show(10); show()
let arr = [next(), next(), next()]
print(arr)
let dict = [next(): next()]
print(dict)
func twice(_ a: @autoclosure () -> Int) -> Int { a() + a() }
counter = 0
print(twice(next()), counter)
assert(next() > 0)
print(counter)
- a c e f g / 5 / dflt2 0 / l m n 7 / d = 1 / d = 2 / d = 10 / d = 3 / [4, 5, 6] / [7: 8] / 3 2 / 3
- a b c e f g / 5 / dflt dflt2 0 / l m n 7 / d = 1 / d = 1 / d = 10 / d = 1 / [1, 2, 3] / [1: 2] / 2 2 / 2
- a c e f / 5 / dflt2 0 / n m l 7 / d = 1 / d = 2 / d = 10 / d = 3 / [4, 5, 6] / [8: 7] / 3 2 / 3
Mental model. A shopper who stops reading the menu after finding a dish they like: the later dishes are never looked at.
Operands evaluate left to right, but &&, || and ?? take their right side as an @autoclosure, so it runs only when needed: a c e f g shows b and d never ran, and && binds tighter than ||, so e || f && g evaluates f then g. 5 ?? v(...) never calls the default; nil ?? v(...) does. Array and dictionary literals evaluate elements in order, key before value ([7: 8]).
Default argument expressions run at every call that omits the argument: show() three times gives 1, 2, 3 with the explicit show(10) not consuming one. An autoclosure parameter runs every time the callee calls it (a() + a() gives 1 + 2 = 3, counter 2). The condition of assert is an autoclosure too: it runs in a debug build (counter becomes 3 here) and is removed in an optimised build (I also ran it with -O and got 2), so never put side effects in it.
Short-circuit and ?? use autoclosures; defaults run per call; assert runs only in debug.
Common traps
- Side effects inside assert. They vanish in release.
- Assuming a default argument is evaluated once. It is per call.
They might follow up with
- Which order is for assignment targets such as a[f()] = g()? Not asserted here; avoid depending on it.
Related questions
- 1.5.7 What is @autoclosure? When is it justified?
- 1.8.4 What does defer guarantee? What is the order of multiple defers?
← In what order do willSet and didSet run across a subclass, an… Which method runs through a protocol extension: when is a call… →