There are two answers depending on what you're asking.
-
When the withoutActuallyEscaping
closure's scope ends, it checks the closure's reference count to ensure that it didn't actually escape. So you're not going to be able to access your stack allocated struct after it's freed, and value semantics can rely on that.
-
Exclusivity enforcement ensures that your stack allocated variable cannot be "reentrantly" accessed...
It is hard to prove that the compiler catches all the possible violations involving withoutActuallyEscaping
, but I'm claiming that it does! If you can find any holes in the diagnostics, please file bugs.
It's also hard for me to explain why it works. I'll try briefly...
Let's consider @escaping
and @noescape
to be part of the function type. Any closure can be passed as either type by upcasting to @escaping
or downcasting to @noescape
using withoutActuallyEscaping
. So, the closure's function type is at any given point is independent of how exclusivity is enforced.
Instead, at the point the closure is defined, we need to decide whether it is obviously-never-escaping. SILGen currently makes an overly conservative guess. If this check fails, users aren't allowed to capture inout
.
Later, the SIL compiler more aggressively determines if the closure is locally-never-escaping. As long as this is a superset of the obviously-never-escaping closures, the representation is still correct. The locally-never-escaping closures are statically checked for exclusivity. The rest will be emitted with dynamic checks whenever they access a captured variable.
So, I can define an obviously-never-escaping closure like this:
func definesClosure(arg: inout Int) {
escapesClosure(&arg) { arg = 3 }
}
And actually escape it like this:
func takesEscaping(_ f: @escaping () -> ()) { f() }
func escapesClosure(_ x: inout Int, _ f: () -> ()) {
withoutActuallyEscaping(f) {
x = 4
takesEscaping($0)
x = 4 // compiler can delete this assignment
}
}
But, since the closure locally never escaped, you will get a compile-time error:
./without_actually_escaping.swift:13:18: error: overlapping accesses to 'arg', but modification requires exclusive access; consider copying to a local variable
escapesClosure(&arg) { arg = 3 }
^~~~~~~~~
./without_actually_escaping.swift:13:40: note: conflicting access is here
escapesClosure(&arg) { arg = 3 }
~~~~~~~~~^~~