An interesting finding: the two loops are equivalent

const std = @import("std");

pub fn main() void {
	// loop 1
	{
		var i: usize = 0;
		while (i < 3) : (i += 1) blk: {
			std.debug.print("{}\n", .{i});
			break :blk;
		} else {
			std.debug.print("end\n", .{});
		}
	}
	
	// loop 2
	{
		var i: usize = 0;
		blk: while (i < 3) : (i += 1) {
			std.debug.print("{}\n", .{i});
			continue :blk;
		} else {
			std.debug.print("end\n", .{});
		}
	}
}

I see why:

  • the break version has the blk inside the while, label the expression that is the body of the loop, so a break there in side the loop machinery
  • the continue version labels the entire while expression, and does the exact same thing: I would expect continue to be implemented that way, actually

So it makes sense, just the syntax makes it quite unobvious, and is very surprising.

Curious looking code but expected behavior. The break leves a labeled block and has no interaction with the loop itself. The continue actually does continue the loop and has nothing to do with the block containing it, which has no label. So this is possible in Zig given that both loops and blocks can have labels and break works with both labeled loops and blocks.

considering you can return from a label with a value, I thought it would be an expression and guessed it would parse like this:

while(cond) expr // notice no braces

not

while(cond)  { statement_list }

I believe you’re right. The thing is that in the first case, you have an expression in the form of a labeled block with curly braces that looks like a loop body but is just a single expression; and that expression evaluates to void, which is also the case for the else body and both bodies in the second loop. Basically this is to say that a loop body with braces is the same as a labeled block that evaluates to void (returns (or breaks) nothing.)

1 Like