Proposal: disallow implicit references to labeled loops in `break` and `continue`
Today, the following is valid Zig:
outer: while (true) {
while (inner_cond) {
if (something) {
break :outer;
}
}
if (something_else) {
break;
}
}
Code like that may be a little confusing in some cases; particularly in a long function spanning multiple screens, it's not necessarily immediately obvious what a break refers to. This gets worse if you consider a refactor which eliminates the inner loop:
outer: while (true) {
// I refactored this code, and now the inner loop is gone
if (something) {
// ...but I failed to notice that the label isn't needed here!
break :outer;
}
if (something_else) {
break;
}
}
Now it looks like those two break statements must behave differently, but they actually don't!
Proposal
I propose that if a loop is labeled, we require it to always be referenced via that label. If an unlabeled break or continue would target the loop, a compile error is triggered.
This improves readability, because as soon as you see a labeled loop, you know that all control flow targeting that loop will be explicitly marked as such.
outer: while (true) {
if (something) {
break :outer;
}
if (something_else) {
// error: break implicitly targets labeled loop
// note: to target this loop, specify its label with 'break :outer'
break;
}
}
An alternative proposal would be disallowing redundant labels on break/continue, so that in the above snippet, the first break was an error due to the redundant :outer. However, I believe that proposal is inferior, because:
- It means there are still two ways to target this loop which you need to keep in mind when reading code (which one you will see depends on context)
- It would make it impossible to add a redundant label to a loop for documentation purposes (e.g.
retry: while (true) { ... }withcontinue :retrymay be a nice readability aid even if there are no nested loops)
An alternative proposal would be disallowing redundant labels on
break/continue, so that in the above snippet, the firstbreakwas an error due to the redundant:outer. However, I believe that proposal is inferior, because:
I personally feel this is more in line with other cases in Zig where unnecessary or redundancy is discouraged or disallowed. (like unreferenced locals). Why define a label that is never targeted specifically? Same as with a variable declaration that is never used, that is an error. I do feel your concerns about that are solid and sound though. Maybe a third option would be to make redundant labels a compiler error itself (so not the break, but the label definition outer: while (true) { ... would be the error if there are no inner loops to break out of). But that is probably more complex to implement and analyze.
Either way I think this is a good proposal.