Skip to content

"continue: label" syntax for while loop continuation expression #472

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

Closed
PavelVozenilek opened this issue Sep 13, 2017 · 6 comments
Closed
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. enhancement Solving this issue will likely involve adding new logic or components to the codebase. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@PavelVozenilek
Copy link

PavelVozenilek commented Sep 13, 2017

Current documentation mentions novel feature of while :

var i1: usize = 1;
var j1: usize = 1;
while (i1 * j1 < 2000) : ({ i1 *= 2; j1 *= 3; }) {
    const my_ij1 = i1 * j1;
    assert(my_ij1 < 2000);
}

Seeing it I was confused, because:

  1. The syntax is unique in the language and feels out of place.
  2. Variables could be used before they are (visualy) declared. (I guess.)
  3. The ({...}) is ugly.
  4. Something what logically belongs to the very end (as with for) is placed at the very beginning.

My suggestion is to use continue: label to implement this:

var i1: usize = 1;
var j1: usize = 1;
while (i1 * j1 < 2000) {
    const my_ij1 = i1 * j1;
    assert(my_ij1 < 2000);
  continue:
    i1 *= 2; 
    j1 *= 3;
}

The label would be allowed only inside while loop, at its very end, no continue would be allowed in the block, there would be max 1 such label permitted in while body, it would not be possible to explicitly jump at it or inside it.

Possibly this feature should be forbidden in case that there are less than 2 continue statements in the loop, to keep people from misusing it.

@andrewrk andrewrk added this to the 0.2.0 milestone Sep 13, 2017
@andrewrk andrewrk added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. enhancement Solving this issue will likely involve adding new logic or components to the codebase. labels Sep 13, 2017
@raulgrell
Copy link
Contributor

raulgrell commented Sep 13, 2017

When this first came up, the while loop was closer to the for loop in C - all that was missing was the initializer part of it. Both of them had the condition expression and iteration expression. These three were equivalent:

// C
for (int i = 0; i < n; n++) { }

// Old Zig
var i: u8 = 0;
while(i < n; n += 1) { }

// New Zig
var i: u8 = 0;
while(i < n) : (n += 1) { }

When the idea of making while loops handle nullables appeared, we needed a way to refer to the unwrapped value from inside the iteration expression, but keep a logical visual order of declarations, ie

// instead of
while(maybe; maybe = value.next() ) | value | { }
// write it like this
while(maybe) | value | : ( maybe = value.next() ) { } 

See #357, @thejoshwolfe proposed something similar with the continue keyword/label

I do agree though, the ({}) is pretty darn ugly and the syntax is strange, but it does sort of appear somewhere else - labels and the currently proposed named/labeled blocks. I intended to propose something that made this explicit, where common interation blocks were labeled and defined how the loop should iterate:

var i: usize = 0;
while ( maybe ) | value | 
        forward: {  i += 2 },
        back: { i -= 2 },
        restart: { i = 0 },
        : { i += 1 } { // no label is default continue
    switch(value) {
        0 => {
            continue :forward;
        },
        1 => {
            continue :restart;
        },
        2 => {
            continue;
        },
        //...
        42 => {
            continue :back;    
        },
    }
}

But I'm still not sure where we're going with the labeled blocks and gotos, I felt it would be hard to implement efficiently and in the end I managed to solve problem with regular functions.

@tiehuis tiehuis added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Sep 15, 2017
@thejoshwolfe
Copy link
Contributor

The syntax is unique in the language and feels out of place.

Agreed.

Variables could be used before they are (visualy) declared. (I guess.)

Actually no. The syntax was chosen with this consideration in mind. Please point out if this is actually possible, but I don't think it is.

The ({...}) is ugly.

Well, a simpler form is this, which is far less ugly:

{var i: usize = 0; while (i < arr.len) : (i += 1) {
    // ...
}}

The ({...}) is because someone put a block in the parentheses, which is something you can do in Zig, and that's all working correctly and clearly for what it is.

But see also, the first point about this syntax being generally out of place in Zig.

Something what logically belongs to the very end (as with for) is placed at the very beginning.

This is very debatable.

If source order is to parallel execution order, then yes, it belongs at the end. But that kind of reasoning also leads to reverse polish notation, and by that point we've lost readability.

Another argument is that the continuation expression belongs close to the condition, because the two are closely related, and together they define what the loop is. (However, I couldn't come up with a real example illustrating this in the 20 minutes i spent just now trying to think of one.)

@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Oct 19, 2017
@andrewrk andrewrk changed the title Better syntax for while loop continuation expression "continue: label" syntax for while loop continuation expression Nov 28, 2017
@vanzomerenc
Copy link

vanzomerenc commented Oct 30, 2020

I know this issue has been closed for a long time, but does zig need the extra while loop syntax in the first place? Can't this be done pretty simply and clearly using defer?

How is

    var i: u32 = 0;
    while (i < 10) : (i += 1) {
        if (i % 2 == 0) continue;
        std.debug.warn("iteration {}\n", .{i});
    }

any different from

    var i: u32 = 0;
    while (i < 10) {
        defer i += 1;
        if (i % 2 == 0) continue;
        std.debug.warn("iteration {}\n", .{i});
    }

@SpexGuy
Copy link
Contributor

SpexGuy commented Oct 30, 2020

These have the same behavior for continue, but have different behaviors if you exit the loop via break.

    var i: u32 = 0;
    while (i < 10) : (i += 1) {
        if (i % 2 == 0) continue;
        if (i == 7) break;
        std.debug.warn("iteration {}\n", .{i});
    }
    assert(i == 7);
    var i: u32 = 0;
    while (i < 10) {
        defer i += 1;
        if (i % 2 == 0) continue;
        if (i == 7) break;
        std.debug.warn("iteration {}\n", .{i});
    }
    assert(i == 8);

@daurnimator
Copy link
Contributor

Can't this be done pretty simply and clearly using defer?

defers would also run if there was an early return in the loop (including via try)

@vanzomerenc
Copy link

Ah, I can see how that would cause some nasty surprises if i was used later, or if that defer statement had some more important side effect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Implementing this issue could cause existing code to no longer compile or have different behavior. enhancement Solving this issue will likely involve adding new logic or components to the codebase. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

8 participants