# Compile-Time Loops

### Compile-Time Loops

A compile-time loop is a loop that runs while Zig is compiling your program.

The loop does not run after the final executable starts. The compiler runs it earlier, uses the result, and then produces normal machine code.

This is useful when the loop works with information that is already known: types, fixed lists, array sizes, field names, enum tags, build options, or constant values.

#### Runtime Loops

A normal loop usually runs at runtime.

```zig
const std = @import("std");

pub fn main() void {
    var i: usize = 0;

    while (i < 3) : (i += 1) {
        std.debug.print("{}\n", .{i});
    }
}
```

This prints:

```text
0
1
2
```

The loop counter `i` exists while the program is running. The program checks `i < 3` each time.

#### Compile-Time Loops

Now place a loop inside a `comptime` block:

```zig
comptime {
    var i: usize = 0;

    while (i < 3) : (i += 1) {
        @compileLog(i);
    }
}
```

This loop runs during compilation.

The final program does not contain this loop. The compiler has already executed it.

`@compileLog` prints messages from the compiler, not from the final program.

#### Looping to Build a Constant

A common use of compile-time loops is building fixed data.

```zig
const squares = comptime blk: {
    var values: [5]u32 = undefined;

    for (0..5) |i| {
        values[i] = @intCast(i * i);
    }

    break :blk values;
};
```

This creates this array during compilation:

```zig
.{ 0, 1, 4, 9, 16 }
```

At runtime, the program already has the finished array. It does not need to compute the squares again.

#### `inline for`

`inline for` asks Zig to unroll the loop at compile time.

Example:

```zig
const std = @import("std");

pub fn main() void {
    inline for (.{ "red", "green", "blue" }) |name| {
        std.debug.print("{s}\n", .{name});
    }
}
```

This behaves like writing:

```zig
std.debug.print("{s}\n", .{"red"});
std.debug.print("{s}\n", .{"green"});
std.debug.print("{s}\n", .{"blue"});
```

The loop is not a normal runtime loop. The compiler expands each iteration.

#### Why `inline for` Matters

A normal `for` loop uses one body repeatedly.

An `inline for` creates a separate body for each item.

That matters when each iteration has a different type.

```zig
const std = @import("std");

pub fn main() void {
    inline for (.{ i32, bool, f64 }) |T| {
        std.debug.print("{s}\n", .{@typeName(T)});
    }
}
```

Here, each item is a type.

Types are compile-time values. The compiler must know `T` for each iteration. `inline for` makes that possible.

The output is:

```text
i32
bool
f64
```

#### Normal Loops Cannot Iterate Over Types at Runtime

This does not make sense as a runtime loop:

```zig
for (.{ i32, bool, f64 }) |T| {
    std.debug.print("{s}\n", .{@typeName(T)});
}
```

The values `i32`, `bool`, and `f64` are types. They belong to compile time. They are not ordinary runtime values.

Use `inline for` when the loop body depends on compile-time values such as types.

#### Compile-Time Loops with `switch`

Compile-time loops are useful with type-based branching.

```zig
const std = @import("std");

fn printDefaults() void {
    inline for (.{ bool, u8, i32 }) |T| {
        const value: T = switch (T) {
            bool => false,
            u8 => 0,
            i32 => 0,
            else => unreachable,
        };

        std.debug.print("{s}: {}\n", .{ @typeName(T), value });
    }
}
```

The compiler expands the loop once for each type.

Conceptually, it becomes:

```zig
{
    const value: bool = false;
    std.debug.print("{s}: {}\n", .{ "bool", value });
}
{
    const value: u8 = 0;
    std.debug.print("{s}: {}\n", .{ "u8", value });
}
{
    const value: i32 = 0;
    std.debug.print("{s}: {}\n", .{ "i32", value });
}
```

This is not dynamic type inspection. The compiler already knows each type.

#### Repeating Code Without Macros

Many languages use macros for this kind of work.

Zig usually uses `inline for` and `comptime`.

Example:

```zig
fn isInteger(comptime T: type) bool {
    inline for (.{ u8, u16, u32, u64, i8, i16, i32, i64 }) |Int| {
        if (T == Int) {
            return true;
        }
    }

    return false;
}
```

Usage:

```zig
const a = isInteger(u32); // true
const b = isInteger(bool); // false
```

The loop runs during compilation because `T` is a compile-time type.

#### Compile-Time Loops Can Reject Code

You can use a compile-time loop to enforce rules.

```zig
fn requireInteger(comptime T: type) void {
    inline for (.{ u8, u16, u32, u64, i8, i16, i32, i64 }) |Int| {
        if (T == Int) {
            return;
        }
    }

    @compileError("expected an integer type");
}
```

This works:

```zig
requireInteger(i32);
```

This fails during compilation:

```zig
requireInteger(bool);
```

The program does not reach runtime. Zig rejects the wrong type before producing the executable.

#### Building a Lookup Table

Suppose you want a table of byte values converted to hexadecimal characters.

```zig
fn makeHexTable() [16]u8 {
    comptime var table: [16]u8 = undefined;

    inline for (0..16) |i| {
        table[i] = if (i < 10)
            '0' + i
        else
            'a' + (i - 10);
    }

    return table;
}

const hex_chars = makeHexTable();
```

The final `hex_chars` value is known at compile time.

It is equivalent to:

```zig
const hex_chars = [_]u8{
    '0', '1', '2', '3',
    '4', '5', '6', '7',
    '8', '9', 'a', 'b',
    'c', 'd', 'e', 'f',
};
```

The loop helped you write the table clearly, but the runtime program receives the completed data.

#### `inline while`

Zig also supports `inline while`.

```zig
const std = @import("std");

pub fn main() void {
    comptime var i: usize = 0;

    inline while (i < 3) : (i += 1) {
        std.debug.print("{}\n", .{i});
    }
}
```

The compiler expands each loop iteration.

This is less common than `inline for`, but it is useful when the loop shape is easier to express with a counter.

#### Runtime Cost

A compile-time loop can reduce runtime cost because the work happens before the program starts.

But this does not mean every loop should become compile-time.

Use compile-time loops for program structure and fixed data.

Use runtime loops for real input.

Good compile-time loop uses:

```text
types
fields
enum tags
fixed tables
static configuration
known sizes
```

Good runtime loop uses:

```text
user input
file data
network data
database rows
command-line arguments
current time
```

#### A Common Mistake

Beginners sometimes try to use `inline for` for speed.

```zig
inline for (items) |item| {
    process(item);
}
```

This only works when `items` is compile-time known.

If `items` comes from a file or user input, it belongs to runtime. A normal `for` loop is correct.

Compile-time looping is about when information is known, not about making every loop faster.

#### Mental Model

A normal loop says:

```text
Repeat this while the program runs.
```

A compile-time loop says:

```text
Repeat this while the compiler builds the program.
```

An `inline for` says:

```text
Create one copy of this code for each compile-time item.
```

That is the core idea.

Compile-time loops let you write compact source code while still producing specialized runtime code.

