Resolve mismatch between `withoutActuallyEscaping` documentation and function signature

The documentation for withoutActuallyEscaping says:

The escapable copy of closure passed to body is only valid during the call to withoutActuallyEscaping(_:do:). It is undefined behavior for the escapable closure to be stored, referenced, or executed after the function returns.

I was surprised to see that the signature is not marked @unsafe. I didn't do the digging myself, but ChatGPT says that this was mentioned explicitly and that the rationale is that it is dynamically detected if the closure escapes, and the process is terminated. If this is true, doesn't this make the documentation wrong? And if there is indeed some way to get UB when using this function, shouldn't it be marked @unsafe?

1 Like

It appears to trap deterministically in a simple test at least:

func f(_ fn: () -> ()) -> (() -> ()) {
  return withoutActuallyEscaping(fn) { $0 }
}

func caller() -> (() -> ()) {
  let x = 3
  return f({ print(x) })
}

caller()()

I don't know if this is guaranteed to be the case. I'd say in this case the documentation might be wrong, or at least underspecified, but perhaps we cannot guarantee the trap catches all such invalid usages?

2 Likes

If a call to the closure races with the scope exit, then yeah, we cannot guarantee to diagnose the misuse; it is up to the vagaries of life whether the closure finishes executing before the scope exits.

Then shouldn’t the function be marked @unsafe?

Well, we can’t promise to diagnose the abstract violation of the rules, but we can diagnose the problem dynamically if it happens to occur in this execution. The current check — that there are no outstanding references to the function value on scope exit — should be enough for that, and I think that should be enough to make it safe. It would be safe regardless if the function isn’t @Sendable.

Ah sorry, I wasn’t understanding clearly. You’re saying that even in the race case, if the closure is retained after the scope exits then we reliably trap?

Then I’m back to asking, shouldn’t the documentation be updated to not claim that using withoutActuallyEscaping can cause UB?

1 Like