i've recently learned that default arguments in a call expression are evaluated after 'rvalue' parameters. the following example illustrates this behavior:
func sideEffect<V>(_ v: V) -> V {
print("side effect for: \(v)")
return v
}
func testArgEvalOrder(
a: String = sideEffect("a"),
b: String,
c: String = sideEffect("c")
) {}
testArgEvalOrder(b: sideEffect("b")) // b, a, c
testArgEvalOrder(b: sideEffect("b"), c: sideEffect("c")) // b, c, a
i was rather surprised to learn this, and i'm wondering why exactly things work in this way. the best documentation i've found regarding the current behavior is from this section of SE-411. however, that exposition doesn't really offer motivation for the existing behavior (since that's not really its purpose), and i'm curious what it is.
from doing a bit of 'code archeology' my best theories thus far are:
- the behavior was inherited from the fact that Swift used to allow default arguments to be 'shuffled' at callsites.
- emitting default argument function calls later may allow for better optimization opportunities or improved codegen.
- there's no specific reason – the language doesn't 'officially' commit to a particular behavior, and implementing it this way was easier/beneficial (or something).
grateful for any historical insights anyone may be willing to share! cc @John_McCall – i believe you may have written some of the original code, so wondering if you know any of the motivations here.
investigation notes
- possible first implementation of 'default args emitted after other args': Blaming swift/lib/SILGen/SILGenApply.cpp at bf75beeb7a320e6a77ff8e49cfd03c402392ee3e · swiftlang/swift · GitHub
- some stuff on calling conventions & default arg generators hinting at some optimization ideas: swift/docs/ABI/CallingConvention.rst at e4d57c3bd8bd3f461b1428fe899967aeac8754bd · swiftlang/swift · GitHub