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:
- ✅
comptimekeyword 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.