Lesson 12 of 15

Comptime Generics

Generic Programming via Comptime

Zig does not have a separate generics syntax like C++ templates or Java generics. Instead, it uses comptime type parameters to achieve the same result --- and more, since you can run arbitrary code at compile time to generate types and functions.

Generic Functions

A generic function accepts a comptime T: type parameter and uses T in its signature and body:

fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

// Usage:
const m = max(i32, 10, 20); // 20
const n = max(f64, 3.14, 2.71); // 3.14

The compiler generates a specialized version for each type used. There is no runtime overhead.

Generic Data Structures

Functions that return type are Zig's way of defining generic structs. The returned struct is a completely normal type:

fn Stack(comptime T: type) type {
    return struct {
        items: [256]T = undefined,
        len: usize = 0,

        const Self = @This();

        pub fn push(self: *Self, value: T) void {
            self.items[self.len] = value;
            self.len += 1;
        }

        pub fn pop(self: *Self) T {
            self.len -= 1;
            return self.items[self.len];
        }
    };
}

var stack = Stack(i32){};
stack.push(42);
const val = stack.pop(); // 42

@TypeOf and Type Reflection

@TypeOf(expr) returns the compile-time type of any expression. Combined with @typeInfo, you can inspect types and branch on their properties:

fn isInteger(comptime T: type) bool {
    return switch (@typeInfo(T)) {
        .int, .comptime_int => true,
        else => false,
    };
}

@TypeOf is particularly useful for writing helper functions that adapt to their input:

fn double(value: anytype) @TypeOf(value) {
    return value * 2;
}

const a = double(@as(i32, 5));   // i32: 10
const b = double(@as(f64, 2.5)); // f64: 5.0

anytype Parameters

The anytype keyword tells the compiler to infer the type from the call site. It is syntactic sugar that avoids an explicit comptime T: type parameter when you only need one inferred type:

fn debugPrint(value: anytype) void {
    const T = @TypeOf(value);
    if (@typeInfo(T) == .int) {
        std.debug.print("int: {}\n", .{value});
    } else {
        std.debug.print("other\n", .{});
    }
}

Your Task

  1. Write a generic function fn Pair(comptime T: type) type that returns a struct with two fields: first: T and second: T, and a method pub fn sum(self: @This()) T that returns self.first + self.second.

  2. Write a function fn typeName(comptime T: type) []const u8 that returns "integer" if T is an integer type, "float" if it is a floating-point type, or "other" otherwise. Use @typeInfo to inspect the type.

Zig runtime loading...
Loading...
Click "Run" to execute your code.