{
var i: usize = 0;
while (i < 10) : (i += 1) {
list.append(@intCast(i32, i + 1)) catch @panic("");
}
}
{
var i: usize = 0;
while (i < 10) : (i += 1) {
testing.expectEqual(@intCast(i32, i + 1), list.inner.items[i]);
}
}
edit: this is the pattern that @andrewrk chose to settle with
this is from @Hejsil 's interface demo. also see a reddit thread that mentions this pattern
doing this is to avoid:
var i: usize = 0;
while (i < 10) : (i += 1) {
list.append(@intCast(i32, i + 1)) catch @panic("");
}
var i_2: usize = 0;
while (i_2 < 10) : (i_2 += 1) {
testing.expectEqual(@intCast(i32, i_2 + 1), list.inner.items[i_2]);
}
compare with a C for loop:
for (int i = 0; i < 10; i++) {}
assert(i == 10);
the c for loop has
versus an example from the zig docs:
const assert = @import("std").debug.assert;
test "while loop continue expression" {
var i: usize = 0;
while (i < 10) : (i += 1) {}
assert(i == 10);
}
zig while loop has:
related zen:
_avoid local maximums_
_communicate intent precisely_
_favor reading code over writing code_
_only one obvious way to do things_
_reduce the amount one must remember_
_minimize energy spent on coding style_
status quo inhibits all of these. can we make this better?
Idea: currently, we support this:
while (foo) : (bar) {
baz
}
Should we also support this?
while (init) : (foo) : (bar) {
baz
}
?
Or, alternately,
(init) : while (foo) : (bar) {
baz
}
e.g.
(var a: usize = 0) : while (a < 8) : (a += 1) {
}
A refined variation of the latter might be good, as it has the following properties:
interesting ideas, but
(var a: usize = 0) : while (a < 8) : (a += 1) {}
your proposal has:
so its just like a c for loop
Anyone coming from another C like language is instantly going to know how to write and read loop declarations like C:
for (initialization ; condition; increment) {
body
}
Maybe I'm missing something. Is there a reason to avoid using C style loops?
I assume the purpose of this is to figure out how to declare new variables in the scope of the for loop. If this is the case, JavaScript does something similar:
for (var i = 0; i < 100; i++) {
console.log(i);
}
It becomes worse when we add more variables... :wink: I think starting a new block is a good solution in this condition. Otherwise just give variable names i, i2, i3, i4, etc.
two places to write how the state updates each loop ¯\_(ツ)_/¯
Problem solved if the variables defined in (...) cannot be modified in the loop body {...}. Or use a Range:
for range(1, 10) do_something_ten_times();
// let's specify the type in |...| if the compiler cannot figure it out
for range(begin, end) |value: i8| {
// first, break if (value + 1 > end)
...
// finally, break if (value + 1 > end)
}
// `count` starts from 0
for range(begin, end) |value: i8, count: u8| {
...
}
// `step` can be zero; `for range` does not throw error
for range(begin, end, step) |value: i8| {
... // `step` is hidden and unmodifiable; we use its absolute value
// if (step > 0) break if (value + step > end)
// if (step < 0) break if (value - abs(step) < end)
}
here is the rest of my proposal:
// rename `for` to `foreach`
for each(elements) |value| { ... }
for each(elements) |value, index| { ... }
// optionally add a counter which starts from 0
while (success) |content, count: usize| {
...
}
edit3: only use the absolute value of step in case numbers are unsigned
↑ updated: changed range(min, max) to range(begin, end, non_zero_step)
edit: well, if you really want zeros that's no problem too
An attempt at finding symmetry between if and while
No, you should take for range as one keyword just like forrange (-rr- is weird). Although we can also write for (@range(x, y, step)) |v, i| but I prefer foreach, then foreach (@range(x, y, step)) |v, i| is a bit verbose. Anyway I can accept both for range/each and the original for syntax.
@range() cannot be used with while because it doesn't return bool, optional nor error.
Right now Zig's for loops already act like a foreach. It is possible to implement ranges in compile-time Zig doing something like this:
fn range(comptime T: type, comptime start: usize, comptime end: usize) []T {
const Len = end - start;
comptime {
var result: [Len]T = [_]T{0} ** Len;
var i: usize = 0;
while (i < Len) : (i += 1) {
result[i] = start + i;
}
return result[0..];
}
}
test "range" {
for (range(i64, 0, 10)) |i| {
std.debug.warn("{} ", .{i});
}
}
Output:
0 1 2 3 4 5 6 7 8 9
Caveat this will not work for runtime only array lengths however for already gives you this:
for (runtimeArray) |value, index| {
}
You can also do it at runtime and without reserving static memory like this:
const std = @import("std");
fn rangeHack(len: usize) []void {
// No chance of UB [*]void is a zero-size type
return @as([*]void, undefined)[0..len];
}
pub fn main() void {
for (rangeHack(10)) |_, i| {
std.debug.warn("{} ", .{i});
}
}
Output:
0 1 2 3 4 5 6 7 8 9
But this is definitely a hack.
Anyone coming from another C like language is instantly going to know how to write and read loop declarations like C:
for (initialization ; condition; increment) { body }Maybe I'm missing something. Is there a reason to avoid using C style loops?
I assume the purpose of this is to figure out how to declare new variables in the scope of the
forloop. If this is the case, JavaScript does something similar:for (var i = 0; i < 100; i++) { console.log(i); }
+1 for this question. What is the aversion to C-style for loops? I am okay with a for-range syntax, but I find the C-style for loop much easier to type and easier to read than the current while loop idiom and unfortunately I find myself using that idiom more than for loops as they are.
my only issue with C-style for loops is they have two different places to write the loop body
for (int i = 0; i < 10; i++) {}
// versus
for (int i = 0; i < 10;) {i++;}
isnt this redundant? zig has the same issue
var i : usize = 0;
while (i < 10) {i += 1;}
// versus
var i : usize = 0;
while (i < 10) : (i += 1) {}
my only issue with C-style for loops is they have two different places to write the loop body
for (int i = 0; i < 10; i++) {} // versus for (int i = 0; i < 10;) {i++;}isnt this redundant? zig has the same issue
var i : usize = 0; while (i < 10) {i += 1;} // versus var i : usize = 0; while (i < 10) : (i += 1) {}
for (var i: usize = 0; i < 10; i+=1) is less characters and I find it more natural to write than
var i: usize = 0; while (i < 10) : (i+=1) {. And plus the i is automatically scoped unlike the while idiom (which would require a {} block)!
Alternatively we could have something like for (0..10) |i| { or for (0..10:2) |i| { (where 2 is the step) which is better than C-style for loops. By my main point is that if we don't want to do for-range loops, even C-style for loops are strictly better than the while idiom we have now, unless I am missing something.
isnt this redundant? zig has the same issue
They are not redundant. These three loops have slightly different semantics in Zig:
var curr: ?*Node = getListHead();
// loop 1
while (curr) |node| : (curr = node.next()) {
if (cond_a) { continue; }
if (cond_b) { break; }
}
// loop 2
while (curr) |node| {
defer curr = node.next();
if (cond_a) { continue; }
if (cond_b) { break; }
}
// loop 3
while (curr) |node| {
if (cond_a) { continue; }
if (cond_b) { break; }
curr = node.next();
}
Loop 1: curr = node.next() will execute on continue but not on break
Loop 2: curr = node.next() will execute on both continue and break
Loop 3: curr = node.next() will not execute on continue or break
Use anonymous blocks to limit the scope of variables.
nymous blocks also work well - the label is a comment and the first curly is pushed over where it belongs.
findOrCreateKey: {
var i = 0;
while (...) : (i += 1) {
...
}
}
I realize this issue is closed but after reading some of the comments I see an idea that could address this and other issues. Namely, to provide a way to define symbols for the next statement/expression. Something like this:
with (vars...) <block/statement>
So for loops you'd have:
with (var i : usize = 0) while (i < 10) : (i += 10) {
...
}
And this could also be used to define symbols/aliases for functions:
with (comptime const T = @TypeOf(a).Pointer.child)
pub fn foo(a: var) !T {
var b: T = undefined;
// ...
return T.init(...);
}
Maybe you could use struct literals for the with <struct_literal> but not sure.
I'm not so much a fan of anonymous blocks when you have a lot of nested loops
Desired:
while(std.utils.Range(0,10)) |i| { // statement cannot both be a declaration and a '.next()' call
// do stuff
}
Creating an iterator is not the same as a testing a boolean expression though.
Maybe the for syntax could be changed to expect iterator declarations (with next() / hasNext() "methods") instead of expecting slices, and while could stick to accepting a condition expression returning a bool or an optional.
for(std.utils.SliceIterator(arr[4..10]) |value| {
// do stuff
}
const iterate = std.utils.SliceIterator;
for( iterate(arr[4..10])) |value| {
// do stuff
}
for(std.utils.Range(0,10) ) |i| {
// do stuff
}
Uglier, but more versatile?
There is one thing that is open to changing here which is having zig fmt support this form:
{var i: i32 = 0; while (i < 10) : (i += 1) {
blah();
}}
At that point, it really looks like a C for loop with a bit more characters. Which both means people coming from C like languages will both feel more at home with that syntax form, and leave them wondering why the syntax just does not mimic C.
{var i: i32 = 0; while (i < 10) : (i += 1) { blah(); }}
inconsistent coding style on the spot, nitpicking starts...
While if/while/for/switch all support |capture|, the later three all have their specialty:
while: : (i += 1) where i += 1 is not an expression. for(int i=0;i<10;i+=1) must cryfor: only works on a slice, without too much hidden logic in C++'s for(auto x : container)switch: case const_min ... const_max_inclusive where ... cannot be used elsewhere and .. also not supportedwell, just small complaint. I think for (0..10) |i| is considerable for the use case above. And in case anyone may think this is slippery slope theory, let's drop support for for (slice) |v, i|. This way if/while/for/switch all capture one value, in the meantime also leave some design space for the tuple syntax and structural assignment.
// `i` is optional
for (arr[0..10]) |&v, i| { v.* = i; }
// becomes
for (0..10) |i| { arr[i] = i; }
// now optional `i` is gone
if (opt_tuple) |x| f(x[0]);
// must capture all elements?
if (opt_tuple) |x, y| g(x, y);
// do not need to capture all fields?
if (opt_struct) |.x, .y| g(x, y);
why do we even need : (i += 1) syntax? wouldnt it be better to just remove all those nuances @SpexGuy delineated? to minimize energy spent on coding style @andrewrk
Most helpful comment
You can also do it at runtime and without reserving static memory like this:
But this is definitely a hack.