Revisiting 'break :blk value'

My only remaining gripe with the otherwise lovely Zig syntax is the requirement we name a block in order to ‘break’ a value out of it. Andrew himself called it awkward back in 2018. Given all of the proposals and discussion around this, I’m surprised this quirk appears here to stay. I’ve read through the Github threads on this, and I haven’t been able to understand what is preventing something like the following proposal.

There has been some hesitation regarding possible confusion given the traditional role of break in languages like C. However, the very concept of assigning a variable to a block of code is itself new compared to C. It seems to me this would provide ample justification for defining some additional semantics in service to this new assignment block™. As a C developer myself, the usage and behavior of the break value described below feels intuitive even with non-trivial nesting.

In short:

  • From a function block, we can return
  • From a loop block, we can break and continue
  • So, from an assignment block, why can’t we break value?

In the same way return cares only about functions, and (unlabeled) break cares only about loops, break value would care only about assignments. They would all remain separate.

By default, break refers to the innermost loop. Similarly, break value would by default refer to the innermost assignment. Note that it’s possible for the innermost assignment block to also be a loop, in which case, this feature happens to already be supported as described.

If needed, you may provide a :name to choose a different block. That is in fact exactly what we are currently forced to do. With a formal specification for this new assignment block, I see no reason to continue requiring this.

Here’s a basic example of what I’m describing.

fn exitFromEachBlockType(value: i32) !void { // start of a 'function' block
    while (true) { // start of a 'loop' block
        const x = { // start of an 'assignment' block
            if (value < 0) { // start of a regular ol 'scope' block
                return error.Negative; // exit 'function' block
            } else if (value > 100) {
                break; // exit 'loop' block (no value, traditional break behavior)
            } else {
                break value; // exit 'assignment' block (value included, which imo is just begging to get assigned to x)
            }
        }
        // ...
    }
}

This change would technically be breaking in situations where you had an assignment block inside of an assignment loop and were using an unlabeled break value, but that feels quite unlikely.