SE-0279: Multiple Trailing Closures [Amended]

Many of my thoughts are written out in more detail in Renaming Trailing-closure Functions in the Standard Library - #7 by allevato and later in that thread, and I've tried to summarize them below.

I still feel strongly that the syntax in the current revision of the proposal is a vast improvement over the original version. However, my advocacy for this solution in the first review was also predicated on the idea that it would allow the first (or only) trailing closure's label to be written out, which would remove the syntactic ambiguity when using a function call with a trailing closure in a conditional statement. However, that wasn't done here; by omitting that possibility, the syntax misses out on a possible solid improvement to the language.

The issue is simply this: Swift's trailing closure syntax was designed with single trailing closures in mind. If we want API authors to now write functions that use multiple closures and use trailing closure syntax, we need a holistic solution that unifies trailing closure syntax everywhere in the language. Grafting new bits of syntax to the existing system with its pre-existing exceptions is just going to make the language more complex and confusing.

If I could design what I think are ideal rules for trailing closures from the ground up, they'd be something like this:

  • All closure arguments in a function call which are not followed by a non-closure argument can be written using trailing closure syntax. Multiple trailing closures would be expressed through juxtaposition, as proposed here.
  • If there are no arguments preceding the trailing closure(s), the empty parentheses may be omitted (as they can be today).
  • If a trailing closure argument is declared with a label, it must be specified, regardless of its position (i.e., including the first).
  • If a trailing closure argument omits its label in its declaration (uses _), then the first such closure at the call site omits the label entirely; all subsequent closures must be labeled _:.

This would make trailing closure argument labels act almost like all other argument labels. Sadly it doesn't fix all cases of the conditional statement problem—if a function's first closure is unlabeled, it'll still be ambiguous, but in that case, surrounding it by parentheses is probably less ugly than other alternatives I can think of (like allowing _: to be in the first position), and it still solves the problem for labeled closures, of which there are many.

Allowing the first trailing closure to have a label would also fix a major problem for API designers: APIs designed to be used with trailing closures cannot provide any meaningful information through a label about what that closure does. Is it a completion callback? Is it a transformation function? Is it something else entirely? You have to guess from the context, and sometimes it's not obvious. The language's choice to unconditionally elide the label removes the ability of the API designer to make that label meaningful. The thread linked above points this out for a handful of standard library functions that take a single closure argument by proposing to rename the functions to graft the label onto the basename, but IMO that makes those functions look worse, and it does nothing to solve the problem for functions that have arguments preceding the closure, where there is no place you can graft the label onto.

Unfortunately, what I don't have is a great idea of how to balance these concerns with the source compatibility requirement.

Overall, however, I do think this proposal is syntactically a huge improvement over the first iteration, and that this would be a good improvement for emerging multi-closure APIs. But I think some of the details still need to be tweaked. As some folks have mentioned upthread, allowing the first trailing closure's label to be optionally specified (even when there is only one trailing closure) would resolve some of the syntactic ambiguities of conditional statements, and it would give API designers the ability to make that label meaningful again. Then, perhaps a future language mode could start making the first label mandatory as well.

11 Likes