It's a bit splitting hairs, but no closure record is formed by the compiler until a call site is seen. While this can be seen as merely an optimization for local functions in isolation, it's really necessary for it to work this way for mutually-recursive local functions to work practically with the reference counting and strict initialization semantics Swift requires. If you have:
func outer(x: inout T) {
var y = 0
func foo() {
print(x)
bar()
}
func bar() {
print(y)
foo()
}
bar()
}
Then for foo
and bar
to be able to call each other, it wouldn't work out if the foo
or bar
declarations immediately formed a closure record at the declaration site, because they need to be able to access each other's captured variables x
and y
. If foo
captured only x
at its declaration site, then bar
captured only y
at its call site, neither would have enough context to invoke the other. And if you did this in the somewhat more traditional way of having foo
and bar
capture each other's closures, you would have both a reference cycle, causing a leak if naively implemented with reference counting, and a circular initialization problem building the mutually-recursive closure objects. Furthermore, access to the inout argument x
must be restricted to nonescaping call sites in order not to violate the lifetime restriction on x
. To maximize the expressivity of local functions and closures, and also coincidentally optimize immediate call sites and recursive local functions to reduce the number of closure allocations necessary, it turns out to be simpler to defer the closure formation for local functions to their actual use site—when you invoke bar
immediately, or pass it as a function value, only then does the compiler crawl through its transitive dependencies to figure out what context needs to be captured along with the function to call it.