Inline Loops
const std = @import("std");
const expect = std.testing.expect;
test "inline for over a tuple" {
// A tuple's elements have different types, so the loop body must be
// compiled separately for each. That is what `inline` provides.
const tuple = .{ @as(u8, 1), @as(f32, 2.5), true };
var count: usize = 0;
inline for (tuple) |value| {
if (@TypeOf(value) == bool) count += 10 else count += 1;
}
try expect(count == 12);
}
test "inline for over types" {
const types = [_]type{ u8, u16, u32 };
var total: usize = 0;
inline for (types) |T| {
total += @sizeOf(T);
}
try expect(total == 7);
}
test "the loop variable becomes comptime-known" {
var sum: usize = 0;
inline for (0..4) |i| {
// `i` is comptime here, so it can be used where a comptime value
// is required, such as an array type's length.
const arr: [i]u8 = undefined;
sum += arr.len;
}
try expect(sum == 6); // 0 + 1 + 2 + 3
}inline for unrolls at compile time. That is occasionally about performance,
but the real reason it exists is types.
Iterating heterogeneous things
A tuple’s elements have different types. A normal loop body is compiled once,
so it cannot handle that. An inline for body is compiled separately per
iteration, so @TypeOf(value) can differ each time:
inline for (tuple) |value| {
if (@TypeOf(value) == bool) { ... }
}
This is how std.debug.print walks its arguments, and how serialisation code
walks a struct’s fields.
The loop variable becomes comptime-known
Inside inline for (0..4) |i|, i is a comptime value, so it can be used
where the compiler demands one (array lengths, type construction, field
names):
const arr: [i]u8 = undefined; // only valid because i is comptime
Use it deliberately
Unrolling multiplies generated code by the iteration count. For a 3-element
tuple that is free; for inline for (0..10000) it is a compile-time and
binary-size disaster. Reach for inline when you need comptime-ness, not as a
performance reflex.