-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
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)