while
A while loop is used to repeatedly execute an expression until some condition is no longer true.
while.zig
const expect = @import("std").testing.expect;
test "while basic" {
var i: usize = 0;
while (i < 10) {
i += 1;
}
try expect(i == 10);
}
Shell
$ zig test while.zig
1/1 test.while basic... OK
All 1 tests passed.
Use break
to exit a while loop early.
while.zig
const expect = @import("std").testing.expect;
test "while break" {
var i: usize = 0;
while (true) {
if (i == 10)
break;
i += 1;
}
try expect(i == 10);
}
Shell
$ zig test while.zig
1/1 test.while break... OK
All 1 tests passed.
Use continue
to jump back to the beginning of the loop.
while.zig
const expect = @import("std").testing.expect;
test "while continue" {
var i: usize = 0;
while (true) {
i += 1;
if (i < 10)
continue;
break;
}
try expect(i == 10);
}
Shell
$ zig test while.zig
1/1 test.while continue... OK
All 1 tests passed.
While loops support a continue expression which is executed when the loop is continued. The continue
keyword respects this expression.
while.zig
const expect = @import("std").testing.expect;
test "while loop continue expression" {
var i: usize = 0;
while (i < 10) : (i += 1) {}
try expect(i == 10);
}
test "while loop continue expression, more complicated" {
var i: usize = 1;
var j: usize = 1;
while (i * j < 2000) : ({ i *= 2; j *= 3; }) {
const my_ij = i * j;
try expect(my_ij < 2000);
}
}
Shell
$ zig test while.zig
1/2 test.while loop continue expression... OK
2/2 test.while loop continue expression, more complicated... OK
All 2 tests passed.
While loops are expressions. The result of the expression is the result of the else
clause of a while loop, which is executed when the condition of the while loop is tested as false.
break
, like return
, accepts a value parameter. This is the result of the while
expression. When you break
from a while loop, the else
branch is not evaluated.
while.zig
const expect = @import("std").testing.expect;
test "while else" {
try expect(rangeHasNumber(0, 10, 5));
try expect(!rangeHasNumber(0, 10, 15));
}
fn rangeHasNumber(begin: usize, end: usize, number: usize) bool {
var i = begin;
return while (i < end) : (i += 1) {
if (i == number) {
break true;
}
} else false;
}
Shell
$ zig test while.zig
1/1 test.while else... OK
All 1 tests passed.
Labeled while
When a while
loop is labeled, it can be referenced from a break
or continue
from within a nested loop:
test_nested_break.zig
test "nested break" {
outer: while (true) {
while (true) {
break :outer;
}
}
}
test "nested continue" {
var i: usize = 0;
outer: while (i < 10) : (i += 1) {
while (true) {
continue :outer;
}
}
}
Shell
$ zig test test_nested_break.zig
1/2 test.nested break... OK
2/2 test.nested continue... OK
All 2 tests passed.
while with Optionals
Just like if expressions, while loops can take an optional as the condition and capture the payload. When null is encountered the loop exits.
When the |x|
syntax is present on a while
expression, the while condition must have an Optional Type.
The else
branch is allowed on optional iteration. In this case, it will be executed on the first null value encountered.
while.zig
const expect = @import("std").testing.expect;
test "while null capture" {
var sum1: u32 = 0;
numbers_left = 3;
while (eventuallyNullSequence()) |value| {
sum1 += value;
}
try expect(sum1 == 3);
var sum2: u32 = 0;
numbers_left = 3;
while (eventuallyNullSequence()) |value| {
sum2 += value;
} else {
try expect(sum2 == 3);
}
}
var numbers_left: u32 = undefined;
fn eventuallyNullSequence() ?u32 {
return if (numbers_left == 0) null else blk: {
numbers_left -= 1;
break :blk numbers_left;
};
}
Shell
$ zig test while.zig
1/1 test.while null capture... OK
All 1 tests passed.
while with Error Unions
Just like if expressions, while loops can take an error union as the condition and capture the payload or the error code. When the condition results in an error code the else branch is evaluated and the loop is finished.
When the else |x|
syntax is present on a while
expression, the while condition must have an Error Union Type.
while.zig
const expect = @import("std").testing.expect;
test "while error union capture" {
var sum1: u32 = 0;
numbers_left = 3;
while (eventuallyErrorSequence()) |value| {
sum1 += value;
} else |err| {
try expect(err == error.ReachedZero);
}
}
var numbers_left: u32 = undefined;
fn eventuallyErrorSequence() anyerror!u32 {
return if (numbers_left == 0) error.ReachedZero else blk: {
numbers_left -= 1;
break :blk numbers_left;
};
}
Shell
$ zig test while.zig
1/1 test.while error union capture... OK
All 1 tests passed.
inline while
While loops can be inlined. This causes the loop to be unrolled, which allows the code to do some things which only work at compile time, such as use types as first class values.
test_inline_while.zig
const expect = @import("std").testing.expect;
test "inline while loop" {
comptime var i = 0;
var sum: usize = 0;
inline while (i < 3) : (i += 1) {
const T = switch (i) {
0 => f32,
1 => i8,
2 => bool,
else => unreachable,
};
sum += typeNameLength(T);
}
try expect(sum == 9);
}
fn typeNameLength(comptime T: type) usize {
return @typeName(T).len;
}
Shell
$ zig test test_inline_while.zig
1/1 test.inline while loop... OK
All 1 tests passed.
It is recommended to use inline
loops only for one of these reasons:
- You need the loop to execute at comptime for the semantics to work.
- You have a benchmark to prove that forcibly unrolling the loop in this way is measurably faster.
See also: