Trailing closure syntax confusion

What am I failing to understand that allows

guard samples.contains(where: { $0.isInfinite }) else { return }

and

guard (samples.contains{ $0.isInfinite }) else { return }

but not

guard samples.contains{ $0.isInfinite } else { return }

The last gives

Anonymous closure argument not contained in a closure
Cannot convert value of type '((Sample) throws -> Bool) throws -> Bool' to expected condition type 'Bool'
Closure expression is unused
Consecutive statements on a line must be separated by ';'
Expected 'else' after 'guard' condition

1 Like

Swift doesn't support trailing closure syntax in guards or ifs unless you wrap it somehow, either by not using trailing closure syntax at all or wrapping the expression in parens, as you did in the second example.

3 Likes

if does better. (It's even a warning, not an error). Should it?

Trailing closure in this context is confusable with the body of the statement; pass as a parenthesized argument to silence this warning

if samples.contains { $0.isInfinite } { } else { return }

Is the message even to be believed? This has always been necessary as far as I remember, but I don't know why it would be problematic.

As that warning alludes to, it can be tough for a human to read. Similarly, it can be hard for a compiler to parse, since allowing trailing closures would also allow multiple trailing closures, leading to difficulty distinguishing multiple levels of braces.

if multipleClosures { } second: { } third: {}, secondClosure { } { }

It may be possible to support it (I know nothing about the parser), but it would be difficult to justify spending the time.

1 Like

This difference that Jessy raises between guard and if is, IMO, confusing. This difference also was confusing in ViewBuilders.

1 Like

This is a bug. This should be diagnosed as a warning just like if case:

test.swift:9:23: warning: trailing closure in this context is confusable with the body of the statement; pass as a parenthesized argument to silence this warning
  if samples.contains { $0.isInfinite } { return }
                     ~^
                     (where:           )

@RonAvitzur Could you file a bug to https://bugs.swift.org ?

Sure. However, it is slightly different. While guard is using the same condition grammar that if uses, which is why the trailing closure is prohibited here, since guard has no then clause the trailing closure is not actually ambiguous, so it either requires a slightly different diagnostic, or a relaxation of the grammar to allow the trailing closure in a guard condition.

2 Likes

As shown above, guards look worse with the extra parentheses. It's visual noise. I think it's the motivation for multiple trailing closures, and the syntax just hasn't made its way back into control flow statements yet.

Multiple trailing closure support makes me think that it's feasible.

You can't really reverse engineer guard, but you can do something like this:

try samples.guardContains { $0.isInfinite } else: { throw NSError() }
extension Sequence {
  func guardContains(
    _ predicate: (Element) -> Bool,
    else throwError: () throws -> Void
  ) throws {
    if !contains(where: predicate) {
      try throwError()
    }
  }
}

I still think if statements would be fine with realistic line usage…

if samples.contains { $0.isInfinite } {
  // after-guard/else stuff 
} else {
  return
}

…but it's not as nice a break as with guard's else. With guard, I especially don't see the rationale for the necessity of parentheses, because of else having to be there.

Terms of Service

Privacy Policy

Cookie Policy