Zig Loops
Loops are fundamental control structures in programming. This chapter will cover various loop statements in Zig in detail.
while Loops
Basic while Loop
The while loop repeatedly executes a code block while a condition is true:
const std = @import("std");
pub fn main() void {
var i: i32 = 0;
std.debug.print("Basic while loop:\n", .{});
while (i < 5) {
std.debug.print("i = {}\n", .{i});
i += 1;
}
}while Loop with Continue Expression
Zig's while loop can include a continue expression that executes at the end of each iteration:
const std = @import("std");
pub fn main() void {
var i: i32 = 0;
std.debug.print("while loop with continue expression:\n", .{});
while (i < 5) : (i += 1) {
std.debug.print("i = {}\n", .{i});
}
}while Loop with Optional Values
while loops can handle optional values, ending when the value is null:
const std = @import("std");
fn getNextNumber(current: i32) ?i32 {
if (current >= 5) return null;
return current + 1;
}
pub fn main() void {
var maybe_number: ?i32 = 0;
std.debug.print("while loop with optional values:\n", .{});
while (maybe_number) |number| {
std.debug.print("Number: {}\n", .{number});
maybe_number = getNextNumber(number);
}
}while Loop with Error Handling
while loops can also handle error union types:
const std = @import("std");
const NumberError = error{
TooLarge,
};
fn getNextSafeNumber(current: i32) NumberError!?i32 {
if (current >= 10) return NumberError.TooLarge;
if (current >= 5) return null;
return current + 1;
}
pub fn main() void {
var maybe_number: NumberError!?i32 = 0;
std.debug.print("while loop with error handling:\n", .{});
while (maybe_number) |maybe_num| {
if (maybe_num) |number| {
std.debug.print("Safe number: {}\n", .{number});
maybe_number = getNextSafeNumber(number);
} else {
break;
}
} else |err| {
std.debug.print("Encountered error: {}\n", .{err});
}
}for Loops
Iterating Over Arrays
for loops are primarily used to iterate over arrays, slices, and other iterable objects:
const std = @import("std");
pub fn main() void {
const numbers = [_]i32{ 1, 2, 3, 4, 5 };
std.debug.print("Iterating over array:\n", .{});
for (numbers) |number| {
std.debug.print("Number: {}\n", .{number});
}
}Iterating Over Slices
const std = @import("std");
pub fn main() void {
const fruits = [_][]const u8{ "Apple", "Banana", "Orange", "Grape" };
const slice = fruits[1..3]; // Banana, Orange
std.debug.print("Iterating over slice:\n", .{});
for (slice) |fruit| {
std.debug.print("Fruit: {s}\n", .{fruit});
}
}for Loop with Index
You can get both the index and value simultaneously:
const std = @import("std");
pub fn main() void {
const colors = [_][]const u8{ "Red", "Green", "Blue" };
std.debug.print("for loop with index:\n", .{});
for (colors, 0..) |color, index| {
std.debug.print("Index {}: {s}\n", .{ index, color });
}
}Iterating Over Strings
const std = @import("std");
pub fn main() void {
const text = "Hello";
std.debug.print("Iterating over string:\n", .{});
for (text) |char| {
std.debug.print("Character: {c} (ASCII: {})\n", .{ char, char });
}
}Range Loops
You can use range syntax to create loops:
const std = @import("std");
pub fn main() void {
std.debug.print("Range loop (0 to 4):\n", .{});
for (0..5) |i| {
std.debug.print("i = {}\n", .{i});
}
std.debug.print("Range loop (2 to 6):\n", .{});
for (2..7) |i| {
std.debug.print("i = {}\n", .{i});
}
}Loop Control Statements
break Statement
break is used to exit a loop early:
const std = @import("std");
pub fn main() void {
var i: i32 = 0;
std.debug.print("Loop with break:\n", .{});
while (true) {
if (i >= 3) break;
std.debug.print("i = {}\n", .{i});
i += 1;
}
std.debug.print("Using break in for loop:\n", .{});
for (0..10) |j| {
if (j == 5) break;
std.debug.print("j = {}\n", .{j});
}
}continue Statement
continue is used to skip the current iteration and continue with the next:
const std = @import("std");
pub fn main() void {
std.debug.print("Using continue to skip even numbers:\n", .{});
for (0..10) |i| {
if (i % 2 == 0) continue;
std.debug.print("Odd number: {}\n", .{i});
}
var j: i32 = 0;
std.debug.print("Using continue in while loop:\n", .{});
while (j < 10) : (j += 1) {
if (j % 3 == 0) continue;
std.debug.print("Not divisible by 3: {}\n", .{j});
}
}Nested Loops
Basic Nested Loops
const std = @import("std");
pub fn main() void {
std.debug.print("Multiplication table (partial):\n", .{});
for (1..4) |i| {
for (1..4) |j| {
const product = i * j;
std.debug.print("{} × {} = {}\t", .{ i, j, product });
}
std.debug.print("\n", .{});
}
}Labeled Loop Control
You can use labels to control specific loops:
const std = @import("std");
pub fn main() void {
std.debug.print("Labeled nested loops:\n", .{});
outer: for (0..3) |i| {
for (0..3) |j| {
if (i == 1 and j == 1) {
std.debug.print("Breaking outer loop at ({}, {})\n", .{ i, j });
break :outer;
}
std.debug.print("({}, {})\n", .{ i, j });
}
}
std.debug.print("Outer loop ended\n", .{});
}Labeled continue
const std = @import("std");
pub fn main() void {
std.debug.print("Labeled continue:\n", .{});
outer: for (0..3) |i| {
for (0..3) |j| {
if (j == 1) {
std.debug.print("Skipping rest of outer loop (i={})\n", .{i});
continue :outer;
}
std.debug.print("({}, {})\n", .{ i, j });
}
}
}Loop Expressions
while Expression
while loops can be used as expressions that return values:
const std = @import("std");
pub fn main() void {
var i: i32 = 0;
const result = while (i < 5) : (i += 1) {
if (i == 3) break i * 10;
} else 0;
std.debug.print("while expression result: {}\n", .{result});
}for Expression
for loops can also be used as expressions:
const std = @import("std");
pub fn main() void {
const numbers = [_]i32{ 1, 2, 3, 4, 5 };
const found = for (numbers) |number| {
if (number == 3) break true;
} else false;
std.debug.print("Found number 3: {}\n", .{found});
const sum = for (numbers) |number| {
// for loops can't directly accumulate, this demonstrates syntax
if (number > 10) break number;
} else 0;
std.debug.print("First number greater than 10: {}\n", .{sum});
}Infinite Loops
Using while(true)
const std = @import("std");
pub fn main() void {
var counter: i32 = 0;
std.debug.print("Infinite loop (will exit when counter reaches 3):\n", .{});
while (true) {
std.debug.print("Counter: {}\n", .{counter});
counter += 1;
if (counter >= 3) break;
}
}Loop Optimization
Compile-time Loops
Using comptime allows loops to execute at compile time:
const std = @import("std");
pub fn main() void {
// Compile-time calculation
comptime var sum: i32 = 0;
comptime var i: i32 = 1;
comptime {
while (i <= 10) : (i += 1) {
sum += i;
}
}
std.debug.print("Sum of 1 to 10 (compile-time): {}\n", .{sum});
// Compile-time array generation
const squares = comptime blk: {
var result: [5]i32 = undefined;
for (result, 0..) |*item, index| {
item.* = @intCast((index + 1) * (index + 1));
}
break :blk result;
};
std.debug.print("First 5 square numbers: ");
for (squares) |square| {
std.debug.print("{} ", .{square});
}
std.debug.print("\n");
}Loop Unrolling
For small fixed-iteration loops, the compiler may automatically unroll them:
const std = @import("std");
pub fn main() void {
const data = [_]i32{ 1, 2, 3, 4, 5 };
var sum: i32 = 0;
// Small loops may be unrolled by the compiler
for (data) |value| {
sum += value;
}
std.debug.print("Array sum: {}\n", .{sum});
}Practical Application Examples
Search Algorithm
const std = @import("std");
fn findElement(array: []const i32, target: i32) ?usize {
for (array, 0..) |element, index| {
if (element == target) {
return index;
}
}
return null;
}
pub fn main() void {
const numbers = [_]i32{ 10, 20, 30, 40, 50 };
const target = 30;
if (findElement(&numbers, target)) |index| {
std.debug.print("Found {} at index {}\n", .{ target, index });
} else {
std.debug.print("Did not find {}\n", .{target});
}
}Array Processing
const std = @import("std");
fn processArray(array: []i32) void {
// Multiply all even numbers by 2
for (array) |*element| {
if (element.* % 2 == 0) {
element.* *= 2;
}
}
}
pub fn main() void {
var numbers = [_]i32{ 1, 2, 3, 4, 5, 6 };
std.debug.print("Before processing: ");
for (numbers) |num| {
std.debug.print("{} ", .{num});
}
std.debug.print("\n");
processArray(&numbers);
std.debug.print("After processing: ");
for (numbers) |num| {
std.debug.print("{} ", .{num});
}
std.debug.print("\n");
}String Processing
const std = @import("std");
fn countVowels(text: []const u8) u32 {
var count: u32 = 0;
const vowels = "aeiouAEIOU";
for (text) |char| {
for (vowels) |vowel| {
if (char == vowel) {
count += 1;
break;
}
}
}
return count;
}
pub fn main() void {
const text = "Hello, World!";
const vowel_count = countVowels(text);
std.debug.print("Text \"{s}\" has {} vowels\n", .{ text, vowel_count });
}Loop Best Practices
1. Choose the Right Loop Type
const std = @import("std");
pub fn main() void {
const data = [_]i32{ 1, 2, 3, 4, 5 };
// ✅ Use for when iterating over known collections
std.debug.print("Using for to iterate array: ");
for (data) |value| {
std.debug.print("{} ", .{value});
}
std.debug.print("\n");
// ✅ Use while for conditional loops
var i: i32 = 0;
std.debug.print("Using while for conditional loop: ");
while (i < 5) : (i += 1) {
std.debug.print("{} ", .{i});
}
std.debug.print("\n");
}2. Avoid Infinite Loops
const std = @import("std");
pub fn main() void {
var attempts: i32 = 0;
const max_attempts = 10;
// ✅ Good practice: clear exit condition
while (attempts < max_attempts) : (attempts += 1) {
// Simulate some operation
if (attempts == 3) {
std.debug.print("Operation successful, exiting loop\n", .{});
break;
}
std.debug.print("Attempt {}\n", .{attempts + 1});
}
if (attempts >= max_attempts) {
std.debug.print("Reached maximum attempts\n", .{});
}
}3. Use Loop Control Statements Appropriately
const std = @import("std");
pub fn main() void {
const numbers = [_]i32{ 1, -2, 3, -4, 5, 0, 6 };
var positive_sum: i32 = 0;
for (numbers) |number| {
// Skip negative numbers
if (number < 0) continue;
// Stop when encountering 0
if (number == 0) break;
positive_sum += number;
}
std.debug.print("Sum of positive numbers: {}\n", .{positive_sum});
}Practice Exercises
Exercise 1: Number Processing
const std = @import("std");
pub fn main() void {
// TODO: Use loops to complete the following tasks:
// 1. Calculate the sum of 1 to 100
// 2. Find all prime numbers from 1 to 50
// 3. Calculate the first 10 terms of the Fibonacci sequence
}Exercise 2: Array Operations
const std = @import("std");
pub fn main() void {
var numbers = [_]i32{ 64, 34, 25, 12, 22, 11, 90 };
// TODO: Implement bubble sort algorithm
// TODO: Find the maximum and minimum values in the array
// TODO: Calculate the average of the array
}Exercise 3: Pattern Matching
const std = @import("std");
pub fn main() void {
const text = "Hello, World! This is a test.";
// TODO: Count the occurrences of each character
// TODO: Find the longest word
// TODO: Reverse each word in the string
}Summary
This chapter covered loop structures in Zig in detail:
- ✅
whileloops: conditional loops with continue expression support - ✅
forloops: iterating over arrays, slices, and other iterable objects - ✅ Loop control:
breakandcontinuestatements - ✅ Nested loops and labeled control
- ✅ Loop expressions and return values
- ✅ Compile-time loops and optimization
- ✅ Practical applications and best practices
Mastering loops is a fundamental programming skill. They are essential tools for implementing repetitive operations and data processing. In the next chapter, we'll learn about Zig's control flow statements.