For
For loops are ideal for cases where the number of iterations is known in advance, like when processing an array or when we want to do something a fixed number of times.
In Zig, we create a for loop like this:
for (sequence_of_elements) | element | {
// Code executed for each element in the processed sequence
}
We can also add a range to get the index of each element:
for (sequence_of_elements, 0..) | element, index | {
// executed for each element in the processed sequence
}
Just like we can combine an array with a range, we can also combine multiple arrays. Imagine that in your game, the player arrives at the space base where the best shmup combat models are available, and you want to display the data on screen.
The problem is that the base terminal uses an API that’s a bit… “unfriendly” for Earthlings: the data for the same ship comes in different arrays - one for the model, another for attack power, another for speed.
Luckily, you have Zig’s for loops:
for_spaceships.zig |
|
const std = @import("std"); const print = std.debug.print; pub fn main() void { const a_spaceship = [_][]const u8{ "Fighter", "VViper", "Silver Hawk", "Saint Dragon", "Arrowhead" }; const a_attack = [_]u16{ 200, // single shot - can be double 450, // with powerups 400, // shot + missiles + laser 375, // frontal shot with powerups 600, // powerful laser + Force capsule }; const a_speed = [_]u16{ 150, // slow 300, // fast with powerups 275, // medium speed 200, // slow mecha due to articulated tail 180, // slow }; print("Most popular combat models: \n", .{}); print("-------------------------------------------------\n", .{}); print("#\t| MODEL\t\t| ATTACK\t| SPEED\t \n", .{}); print("-------------------------------------------------\n", .{}); for (a_spaceship, a_attack, a_speed, 0..) // loop to list combined arrays | // data capture s_model, // ship model n_pow, // attack points n_speed, // ship speed n_i, // index | { print("{}\t| {s}\t| {}\t\t| {}\t \n", // print the data .{ n_i + 1, s_model, n_pow, n_speed }); } } |
|
$ zig run for_spaceships.zig |
|
Most popular combat models: ------------------------------------------------- # | MODEL | ATTACK | SPEED ------------------------------------------------- 1 | Fighter | 200 | 150 2 | VViper | 450 | 300 3 | Silver Hawk | 400 | 275 4 | Saint Dragon | 375 | 200 5 | Arrowhead | 600 | 180 |
Excellent! With this combined information, the player can continue their space adventure. In a single for loop, we combined the names, power, and speed of the ships in the catalog, plus an automatic index. In this case, the elements are traversed in parallel: one position from each array forms a group together with the same position in the other arrays.
Now, after listing the ships, the player enters the menu: “Power-ups for the ship.” But here we run into a problem: the database is a mess. Ugh:
- “From entry 8 onward, none of the records are valid.”
- “There are corrupt entries - they usually have 777 in all fields, a value no other item uses,”
- There’s an internal note saying, “The third entry has been deactivated in the system - it can no longer be acquired.”
The power-ups are mixed - they have different properties: attack, defense, and healing points, but the fields that don’t apply to a given type are set to null.
Those should be printed in the list as value 0.
for_powerups.zig |
|
const std = @import("std"); const print = std.debug.print; pub fn main() void { const a_powerups = [_]?[]const u8{ "Turbo Boost", // increases speed "Double Laser", // attack null, // DEACTIVATED !!! null, // CORRUPT DATA !!! "Shield Field", // defense only "Emergency Kit", // restores energy null, // Name unknown but still valid "OverdriveCore", // mega power-up "Deleted", "Deleted", // ...and so on for the rest }; const a_pow_attack = [_]?u16{ null, // Turbo Boost gives no attack 80, // Double Laser null, 777, null, // Shield Field gives no attack null, // Emergency Kit is not offensive null, 180, // Overdrive Core null, null, // ... }; const a_pow_def = [_]?u16{ 5, // Turbo Boost gives some defense null, // Double Laser gives no defense null, 777, 40, // Shield Field null, // Emergency Kit null, // ? 20, // Overdrive Core null, null, // ... }; const a_pow_heal = [_]?u16{ null, // no healing null, // no healing null, 777, null, // Shield Field doesn’t heal 100, // Emergency Kit heals! 200, // ? This was likely a healing item null, // Overdrive Core null, null, // ... }; print("Power ups for the ship: \n", .{}); print("======================================================\n", .{}); print("#\t| NAME\t\t| ATK.\t| DEF.\t| HP.\t \n", .{}); print("======================================================\n", .{}); var n_i: usize = 0; loop: switch (n_i) { else => { for (a_powerups[n_i..], a_pow_attack[n_i..], a_pow_def[n_i..], a_pow_heal[n_i..], n_i..) // loop to list the combined arrays | // data capture s_name, // ship model n_att, // attack points n_def, // defense points n_energy, // energy points n_ix, // index | { // index 2 is deactivated -> skip it if (n_ix == 2) continue; // database wiped // exit after record 8 if (n_ix > 7) break; // if data is corrupt, all fields = 777 if (n_att == 777) { n_i = n_ix + 1; continue :loop n_i; } // handle some null names const s_name_pow: []const u8 = s_name orelse "UNKNOWN"; print("{}\t| {s}\t\t| {} \t| {}\t| {}\t \n", // .{ n_ix + 1, // index s_name_pow, // name n_att orelse 0, // attack points n_def orelse 0, // defense points n_energy orelse 0, // energy }); } }, } } |
|
$ zig run for_powerups.zig |
|
Power-ups for the ship: ====================================================== # | NAME | ATK. | DEF. | HP. ====================================================== 1 | Turbo Boost | 0 | 5 | 0 2 | Double Laser | 80 | 0 | 0 5 | Shield Field | 0 | 40 | 0 6 | Emergency Kit | 0 | 0 | 100 7 | UNKNOWN | 0 | 0 | 200 8 | OverdriveCore | 180 | 20 | 0 |
By using break and continue inside for loops, we can handle null values, exit the loop, jump to a label, return to the start of the loop, and, as you can see, process the data in whatever way suits us best.
If you look closely, there’s a detail we hadn’t seen until now: the keyword orelse, which is used to substitute optional (potentially null) values with default ones:
|
// handling some null names const s_name_pow: []const u8 = s_name orelse "UNKNOWN"; |
This means that when s_name is null, the string “UNKNOWN” is assigned to the constant s_name_pow, and when it’s not null, the actual value it contains - a valid string - is assigned.
The same applies when printing optional values:
|
n_att orelse 0, // attack points n_def orelse 0, // defense points n_energy orelse 0, // energy |
If any of these is null, it’s replaced by 0. If not, their actual numeric value is used. If these variables have the value null, we pass 0 as the argument to print. This way, we can print potentially null values as regular integers without needing to use {?}:
|
// without orelse (would fail if there are nulls) print("{}\t| {s}\t\t| {?} \t| {?}\t| {?}\t \n" // with orelse print("{}\t| {s}\t\t| {} \t| {}\t| {}\t \n" |