Skip to content

Zig Compile-Time

Compile-time computation is one of Zig's most powerful features, allowing code to execute at compile time and generate efficient runtime code.

Compile-Time Basics

comptime Keyword

The comptime keyword marks code to execute at compile time:

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

pub fn main() void {
    // Compile-time constant
    const compile_time_value = comptime 2 + 3;
    std.debug.print("Compile-time calculation: {}\n", .{compile_time_value});
    
    // Compile-time variable
    comptime var counter = 0;
    comptime {
        counter += 1;
        counter *= 2;
    }
    
    std.debug.print("Compile-time variable: {}\n", .{counter});
    
    // Compile-time loop
    comptime var sum = 0;
    comptime var i = 1;
    comptime {
        while (i <= 10) : (i += 1) {
            sum += i;
        }
    }
    
    std.debug.print("Sum of 1 to 10 (compile-time): {}\n", .{sum});
}

Compile-Time Functions

Functions can execute at compile time:

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

// Compile-time Fibonacci calculation
fn fibonacci(n: u32) u32 {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// Compile-time factorial calculation
fn factorial(n: u32) u32 {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

pub fn main() void {
    // Calculate at compile time
    const fib_10 = comptime fibonacci(10);
    const fact_5 = comptime factorial(5);
    
    std.debug.print("Fibonacci 10th term: {}\n", .{fib_10});
    std.debug.print("5 factorial: {}\n", .{fact_5});
    
    // Generate array at compile time
    const fib_array = comptime blk: {
        var array: [10]u32 = undefined;
        for (array, 0..) |*item, i| {
            item.* = fibonacci(@intCast(i));
        }
        break :blk array;
    };
    
    std.debug.print("Fibonacci array: ");
    for (fib_array) |num| {
        std.debug.print("{} ", .{num});
    }
    std.debug.print("\n");
}

Compile-Time Type Operations

Type Reflection

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

const Point = struct {
    x: f32,
    y: f32,
    z: f32,
};

const Color = enum {
    Red,
    Green,
    Blue,
};

fn printTypeInfo(comptime T: type) void {
    const type_info = @typeInfo(T);
    
    std.debug.print("Type: {}\n", .{T});
    std.debug.print("Size: {} bytes\n", .{@sizeOf(T)});
    std.debug.print("Alignment: {} bytes\n", .{@alignOf(T)});
    
    switch (type_info) {
        .Struct => |struct_info| {
            std.debug.print("Struct field count: {}\n", .{struct_info.fields.len});
            for (struct_info.fields) |field| {
                std.debug.print("  Field: {s} (type: {})\n", .{ field.name, field.type });
            }
        },
        .Enum => |enum_info| {
            std.debug.print("Enum value count: {}\n", .{enum_info.fields.len});
            for (enum_info.fields) |field| {
                std.debug.print("  Value: {s}\n", .{field.name});
            }
        },
        .Int => |int_info| {
            std.debug.print("Integer bits: {}\n", .{int_info.bits});
            std.debug.print("Signed: {}\n", .{int_info.signedness == .signed});
        },
        else => {},
    }
    std.debug.print("\n");
}

pub fn main() void {
    comptime {
        printTypeInfo(Point);
        printTypeInfo(Color);
        printTypeInfo(i32);
        printTypeInfo(u64);
    }
}

Generic Functions

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

// Generic function: works with any numeric type
fn add(comptime T: type, a: T, b: T) T {
    return a + b;
}

// Generic function: find maximum in array
fn max(comptime T: type, array: []const T) T {
    if (array.len == 0) return 0;
    
    var maximum = array[0];
    for (array[1..]) |item| {
        if (item > maximum) {
            maximum = item;
        }
    }
    return maximum;
}

// Generic function: swap two values
fn swap(comptime T: type, a: *T, b: *T) void {
    const temp = a.*;
    a.* = b.*;
    b.* = temp;
}

pub fn main() void {
    // Use generic addition
    std.debug.print("Integer addition: {}\n", .{add(i32, 10, 20)});
    std.debug.print("Float addition: {d:.2}\n", .{add(f64, 3.14, 2.86)});
    
    // Use generic maximum
    const int_array = [_]i32{ 1, 5, 3, 9, 2 };
    const float_array = [_]f32{ 1.1, 5.5, 3.3, 9.9, 2.2 };
    
    std.debug.print("Integer array max: {}\n", .{max(i32, &int_array)});
    std.debug.print("Float array max: {d:.1}\n", .{max(f32, &float_array)});
    
    // Use generic swap
    var x: i32 = 100;
    var y: i32 = 200;
    
    std.debug.print("Before swap: x={}, y={}\n", .{ x, y });
    swap(i32, &x, &y);
    std.debug.print("After swap: x={}, y={}\n", .{ x, y });
}

Compile-Time Code Generation

Generate Structs

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

// Generate struct at compile time
fn generateStruct(comptime fields: []const struct { name: []const u8, type: type }) type {
    comptime var struct_fields: [fields.len]std.builtin.Type.StructField = undefined;
    
    comptime {
        for (fields, 0..) |field, i| {
            struct_fields[i] = std.builtin.Type.StructField{
                .name = field.name,
                .type = field.type,
                .default_value = null,
                .is_comptime = false,
                .alignment = @alignOf(field.type),
            };
        }
    }
    
    return @Type(std.builtin.Type{
        .Struct = std.builtin.Type.Struct{
            .layout = .Auto,
            .fields = &struct_fields,
            .decls = &[_]std.builtin.Type.Declaration{},
            .is_tuple = false,
        },
    });
}

pub fn main() void {
    // Define fields
    const fields = [_]struct { name: []const u8, type: type }{
        .{ .name = "id", .type = u32 },
        .{ .name = "name", .type = []const u8 },
        .{ .name = "score", .type = f64 },
    };
    
    // Generate struct type
    const Student = comptime generateStruct(&fields);
    
    // Use generated struct
    const student = Student{
        .id = 12345,
        .name = "Alice",
        .score = 95.5,
    };
    
    std.debug.print("Student information:\n");
    std.debug.print("  ID: {}\n", .{student.id});
    std.debug.print("  Name: {s}\n", .{student.name});
    std.debug.print("  Score: {d:.1}\n", .{student.score});
}

Compile-Time Conditional Compilation

Platform-Specific Code

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

// Compile-time platform detection
const is_windows = comptime builtin.os.tag == .windows;
const is_linux = comptime builtin.os.tag == .linux;
const is_macos = comptime builtin.os.tag == .macos;

// Platform-specific path separator
const path_separator = comptime if (is_windows) "\\" else "/";

// Platform-specific line ending
const line_ending = comptime if (is_windows) "\r\n" else "\n";

// Choose implementation at compile time
fn getPlatformName() []const u8 {
    return comptime if (is_windows)
        "Windows"
    else if (is_linux)
        "Linux"
    else if (is_macos)
        "macOS"
    else
        "Unknown";
}

pub fn main() void {
    std.debug.print("Current platform: {s}\n", .{getPlatformName()});
    std.debug.print("Path separator: {s}\n", .{path_separator});
    std.debug.print("Line ending length: {}\n", .{line_ending.len});
    
    // Compile-time feature detection
    const has_vector_support = comptime builtin.cpu.arch.endian() == .Little;
    std.debug.print("Little endian support: {}\n", .{has_vector_support});
    
    // Compile-time optimization level
    const optimization_level = comptime switch (builtin.mode) {
        .Debug => "Debug",
        .ReleaseSafe => "Release Safe",
        .ReleaseFast => "Release Fast",
        .ReleaseSmall => "Release Small",
    };
    
    std.debug.print("Optimization level: {s}\n", .{optimization_level});
}

Compile-Time Performance Optimization

Precomputed Tables

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

// Precompute sine table at compile time
const SIN_TABLE_SIZE = 360;
const sin_table = comptime blk: {
    var table: [SIN_TABLE_SIZE]f64 = undefined;
    for (table, 0..) |*item, i| {
        const angle = @as(f64, @floatFromInt(i)) * std.math.pi / 180.0;
        item.* = @sin(angle);
    }
    break :blk table;
};

// Fast sine lookup
fn fastSin(degrees: u32) f64 {
    return sin_table[degrees % SIN_TABLE_SIZE];
}

// Precompute CRC table at compile time
const CRC_TABLE = comptime blk: {
    var table: [256]u32 = undefined;
    for (table, 0..) |*item, i| {
        var crc: u32 = @intCast(i);
        for (0..8) |_| {
            if (crc & 1 != 0) {
                crc = (crc >> 1) ^ 0xEDB88320;
            } else {
                crc >>= 1;
            }
        }
        item.* = crc;
    }
    break :blk table;
};

// Fast CRC calculation
fn fastCrc32(data: []const u8) u32 {
    var crc: u32 = 0xFFFFFFFF;
    for (data) |byte| {
        const table_index = @as(u8, @truncate(crc)) ^ byte;
        crc = (crc >> 8) ^ CRC_TABLE[table_index];
    }
    return crc ^ 0xFFFFFFFF;
}

pub fn main() void {
    // Use precomputed sine table
    std.debug.print("Sine values:\n");
    for ([_]u32{ 0, 30, 45, 60, 90 }) |angle| {
        std.debug.print("sin({}°) = {d:.6}\n", .{ angle, fastSin(angle) });
    }
    
    // Use precomputed CRC table
    const test_data = "Hello, Zig!";
    const crc = fastCrc32(test_data);
    std.debug.print("\nCRC32(\"{s}\") = 0x{X}\n", .{ test_data, crc });
}

Summary

This chapter covered Zig's compile-time features in detail:

  • comptime keyword and compile-time execution
  • ✅ Compile-time type operations and reflection
  • ✅ Generic functions and type parameters
  • ✅ Compile-time string processing
  • ✅ Compile-time code generation
  • ✅ Conditional compilation and feature flags
  • ✅ Compile-time memory allocation
  • ✅ Compile-time error handling and validation
  • ✅ Compile-time performance optimization

Zig's compile-time system is its most unique and powerful feature, allowing developers to accomplish significant work at compile time and generate efficient runtime code. In the next chapter, we'll learn about Zig arrays and slices.

Content is for learning and research only.