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:

  • while loops: conditional loops with continue expression support
  • for loops: iterating over arrays, slices, and other iterable objects
  • ✅ Loop control: break and continue statements
  • ✅ 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.