"'break' is only allowed inside a loop, if, do, or switch" error message inside guard statement is misleading

When break is used in a guard statement within a do { } block, the error message states that break can be used in a do block which is quite confusing.

guard

Only when you change the guard statement to an if statement does a more articulate error message appear.

if

My first point: I'd suggest the code checking heuristic to understand this context and provide the same useful error message for the guard statement situation (which would have saved me a lot of time, and no doubt many others :rofl:).

My second point: Is there any semantic reason why an unlabelled break is not usable in a do { } block? I'm not a fan of labelled breaks, so my instinct would be that an unlabelled break would simply break the innermost do scope, in the event of nested scopes. Obviously return is not desirable here because it exits the function. I can only assume there were evolution threads covering edge cases explaining why this was designed the way it is.

It is perfectly valid to use break inside a guard statement. But the break applies to the outer context.

From the language reference:

If that condition isn’t met, the code inside the else branch is executed. That branch must transfer control to exit the code block in which the guard statement appears. It can do this with a control transfer statement such as return , break , continue , or throw , or it can call a function or method that doesn’t return, such as fatalError(_:file:line:) .

The whole point of the guard statement is to guarantee early exit if the condition is false. Allowing to break the do block would go directly against that guarantee.

1 Like

Can you explain this further? I fail to understand how this goes against the guarantee. Wouldn’t the break statement here explicitly exit the outer scope i.e. the do block? Or is the outer scope understood to be the guard else scope? You start by saying that break is valid inside a guard statement but that it applies to the outer scope, by which I understand to be the do block.

Sorry, I misinterpreted the "second point". I though it was about breaking the else.

Forget it.

Don’t worry, I thought I was misunderstanding something ;)

@orchetect:
I agree with your first point. As for your second point my best guess as to the rationale is that users might think writing a break without labels actually exits the if or guard statements’ body instead of their outer scope as you expected (as @Jean-Daniel explained it doesn't make sense inside the guard though). So it is disallowed unless the if statement is inside a loop/switch where one would more often expect the break to apply to the loop/switch? (To avoid boilerplate in the common case maybe?)
I do wonder though why do doesn't get the same treatment that a for and a switch get. I’m not sure, having a definitive answer would be nice.

1 Like

That's all I'm kind of thinking here. It sure would be convenient, and fairly idiomatic, to have a simple break out of a do block.

I've run into this a few times already so either my brain is broken and I need to just think different or this could be a future language addition.

I don't want to pester the engineers but someone might know why this was not originally implemented. :relieved:

There are two possible error messages.

A do statement — without a catch handler — will get the more specific error message: example.

The do-catch statement could be included in findUnlabeledBreakOrContinueStmtTarget:

-    return isa<IfStmt>(S) || isa<DoStmt>(S);
+    return isa<IfStmt>(S) || isa<DoStmt>(S) || isa<DoCatchStmt>(S);

I think the problem is that do is basically structural — it introduces a nested scope, but that's about all it does.

Loop constructs also introduce a scope, but they're more than structural because that scope is (in general) executed multiple times. Unlabelled break from a loop can be thought of as terminating the loop, rather than exiting the scope. (For comparison, continue exits the scope, but it doesn't terminate the loop.)

Thus, it's going to be a bit problematic if you rearrange the code inside a loop, introducing a nested do block, and that changes the semantics of an unlabelled break within the code you're rearranging.

There's another reason, too. Anyone coming to Swift from C or Obj-C (a lot of people) has a predisposition to think that unlabelled break exits only loops, not other kinds of scopes. Such people might, with an unlabelled break inside a do inside a loop, make the wrong assumption about what it does, and it wouldn't necessarily be obvious to them that it's wrong.

(There are other cases where Swift has sacrificed — or might sacrifice — your personal convenience in favor of protecting the masses.)

1 Like

Makes sense.

I find myself wanting to exit a do block for reasons other than just error throws without having to undertake obtuse refactoring, and something idiomatic would be nice.

Is there a problem using a labelled break for this? It's only one small step more than using an unlabelled break.

1 Like