Compile-time checking for completion handler closures

When passing closures to functions in Swift, there is a special kind of closure that we typically name a completionHandler. Every time I have called a function that takes a parameter with this name, it is under the assumption that it is guaranteed to be called exactly one time. Am I correct in making this assumption? When writing one of these functions, you might miss a branch in your code where you forget to call the completionHandler your function is passed as a parameter. Or, you might call it twice. Or, you might call it in a for loop. (whoops) I would like to propose adding an annotation for these completionHandler closures so the compiler can reason about them. The compiler could check that the completionHandler is either called directly in every branch of that function or delegated to another function that also has this annotation for that parameter. I don’t know enough about the Swift compiler to know if this is even feasible. Hopefully it is! This would give us the same return-style checking that we get when returning a value from a function, which would ensure greater code safety. I’m mostly looking for feasibility here before I get started working on a proposal.

I wonder if what you are trying to achieve is better served by async/await (Concrete proposal for async semantics in Swift · GitHub). With that proposal, all completion-style code is done using coroutines with the same guarantees given to return

As for calling the handler at least once: For trivial cases the compiler might be able to handle this, but as soon as the closure is stored somewhere, this sounds like it would quickly become very hard/impossible to do, kind of a variation of the halting problem.

Async/await will probably help reduce accidental occurrences of this, but iirc there was talk about a mechanism to "abandon" a coroutine even there (the equivalent of the completion handler never being called).

Wrt calling the handler no more than once: Async/await should prevent this completely I think. We might also be able to create a solution library-side once we have move-only types and consuming methods/functions by not allowing the completion handler to be copied and by consuming the handler by calling it (potentially by wrapping it in a struct with those semantics internally if these enhancments don't apply directly to function types).

(edit: whoops, of course you can already wrap the handler in a struct and nil it after it being called once, the type system won‘t guarantee it though)

A more lightweight solution to this can be using Continuation Monads, which represent the same thing. It's equivalent to currying the completion handler, and you get a lot of freebies for it (e.g. composition with flatMap).

Thanks, George! I completely forgot that Chris’s async/await already addressed this. We can just wait for that.