zig icon indicating copy to clipboard operation
zig copied to clipboard

Proposal: disallow implicit references to labeled loops in `break` and `continue`

Open mlugg opened this issue 2 months ago • 1 comments

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) { ... } with continue :retry may be a nice readability aid even if there are no nested loops)

mlugg avatar Oct 29 '25 13:10 mlugg

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:

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.

basdp avatar Nov 07 '25 08:11 basdp