-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
make break apply to all blocks, not just loops #2990
Comments
One argument in favor of this is that |
I like... but a few clarifying questions... fn foo(i: u8) u32 {
if (i == 0) {
break; // Q1: jump to end of if-block?
}
if (i == 0) break; // Q2: no if-block, but outer block expects u32: compile error void expecting u32?
if (i == 0) break 0; // Q3: no if-block, but outer block expects u32: return 0?
while (true) {
if (i == 0) break; // Q4: jump to end of while-block?
}
while (it.next()) |value| if (value == 5) break; // Q5: no if-block, jump to next statement after while?
} |
Here are the answers, according to this proposal:
Yes
This would be "error: must use
"error: must use
This is a good question, because what's expected here is for Here's a related example: suspend {
break; // instead of suspending, the async function continues executing
}
// equivalent to:
label: suspend {
break :label; // instead of suspending, the async function continues executing
} suspend label: {
break :label; // skips x +=1 and completes the suspend
x += 1;
} Likewise: while (true) {
if (i == 0) break; // break out of the while loop
}
// equivalent to
label: while (true) {
if (i == 0) break :label; // break out of the while loop
} while (true) label: {
if (i == 0) break :label; // skips over x +=1 and continues the loop
x += 1;
}
// equivalent to
while (true) {
if (i == 0) continue; // skips over x +=1 and continues the loop
x += 1;
} That is a downside of this proposal. This example is a bit inconsistent.
Yes, that |
One big downside of this is that a lot of code that looks like this will break in horrible ways: while (foo) {
if (bar) {
break;
}
} |
Rad! One more example to provide/ask clarification: if (condition) {
break;
} else {
// jumps to here?
}
// or here? I assume the latter, as the former would wreak havoc on this, or at least be super confusing: if (optional) |x| {
break;
} else {
// but here we should assume that `optional` was null!
} Maybe the answer is obvious but I thought I'd bring it up because in this case, |
(in short: i am against this proposal) This is so confusing to me, maybe i am just to used to C's rules that all major languages have adopted. I think that Zig needs unnamed blocks (to get rid of But its probably better to left loop control flow keywords untouched and invent something new, like a new keyword to control blocks, i think I am thinking of a block that is labelled with var x = #{ //unnamed labelled block
yield value;
}; It could also replace named blocks that exist now var x = #label { //not a hashtag
yield #label value;
}; This could be possible (it yields the void..) but i think that blocks and loops should be separate - while (true) #{
if (i == 0) yield; // skips over x +=1 and continues the loop
x += 1;
}
// equivalent to
while (true) {
if (i == 0) continue; // skips over x +=1 and continues the loop
x += 1;
} |
@andrewrk soo you closed it because people should understand zig control flow without being an expert in zig syntax, but your new proposal goes right against this. |
That's right. And the argument I made in #732 (comment) still applies here. There are good reasons for and against this proposal. |
(EDIT: I guess this stray thought developed into an alternative proposal, which takes this use case one step further.) I personally don't quite see a use case for labelling the block inside a loop ( Here's an (imo quite conservative) idea on how to de-clutter scenarios involving loops: Introduce implicit block labels named after their control flow structure tokens (maybe prefixed/suffixed, here with non-optimal var x = while(flag){
// break :while 1; // break from the while, as explicitly specified
// break 2; // would break from the `while` the current block is associated with, or be an error if we want the programmer to be explicit
if(a){
break :while 3; // break from the while loop from within the if's block, as explicitly specified
}
if(b){
break :if 4; // break from the if; unused value triggers compiler error
//as-of-original-proposal equivalent: break 4;
}
} This extends quite nicely to nesting these constructs (to a reasonable degree): while(running()){
for(getElements()) |element| {
switch(element.kind){
.abort => break :while, // it is clear we refer to the while loop
.pause => break :for, // it is clear we refer to the for loop
else => break :switch, // superfluous here, but doesn't hurt to be explicit imo
}
//common handling per else-element...
}
// pause or something once elements have been processed
} With these implicit labels in place, if we required labels to break from "control flow blocks", I think most (all?) ambiguities can be caught by unused value errors: while(true){
var z = if(a) 1 else 2; // correct, obvious solution
var y = if(a) {break :if 1;} else {break :else 2;} // correct, though maybe "else" branches should reuse the label of the "base" structure? (in this case :if)
var x = if(a) {break 1;} else {break 2;}; // error: no label breaking from "control flow block"
var w = if(a) {break :while 1;} else {break :while 2;}; // potentially correct? (although the assignment is technically unreachable code)
var v = while(flag){
var u = {break 4;}; // since break is the only way for a value to reach the assignment, it's probably not meant to apply to the while loop.
{break 4;} // unnecessary nested block -> unused value -> compilation error
}
} ... sorry this got so long. I also see benefits in trying to merge the concepts of |
@hryx the proposal is as you suggested, the latter of your example. |
I think this proposal, if it including the modification to the current behavior of valueless Since normally
However, I think it is extremely confusing and error prone that in this proposal, the above would be different from
This would definitely be extremely confusing to people new to Zig. I think making changes around The only ways I see to solve this are to either
However, I think removing unlabeled The introduction of Since I view |
Currently, to break from a block with a value, one must label the block:
The reason for this is that, in other languages, such as C,
break
applies to the innermost loop or switch statement, and not blocks.This is a conservative decision meant to minimize surprise and confusion at the control flow keyword doing something slightly, but crucially, different.
However, this is an instance of legacy design decisions of C holding Zig back from what may be the best design. Perhaps it's a place where Zig should boldly break expectations (:drum: :drum: :boom: ) and do it how it should have been done since the beginning.
If this proposal is accepted, then
break
will always apply to the innermost loop or block, and the example can be rewritten like this:The text was updated successfully, but these errors were encountered: