Compile time and the type-function pattern in Zig
Sep. 24, 2025
In Zig, instead of a collection of specialized language features,
comptime is the engine for a type driven development approach.
comptime allows the use of Zig regular code to generate, validate, or
manipulate types at compile time.
The type-function is a basic pattern and illustrates the Zig's
comptime feature. This kind of function returns a type that is based
on the provided arguments. This is the way to implement generics in Zig.
The following example shows a Vector function that returns a struct
containing an array which type and size attributes are defined based on
the arguments passed to the function. It also defines the dot
operation valid for the generated type.
pub fn Vector(comptime n: usize, comptime T: type) type {
return struct {
data: [n]T,
pub fn dot(self: @This(), other: @This()) T {
var sum: T = 0;
for (0..n) |i| {
sum += self.data[i] * other.data[i];
}
return sum;
}
};
}
When this function is called, it will be evaluated a compile time. For
example, the following test creates a new type for a vector of three
integers: Vec3. This is a generated type that can be used to create new
values and perform operations among then. The test shows how
two values are defined, the dot operation is applied and the result is
confirmed.
test "Valid vector operations" {
const Vec3 = Vector(3, i32); // Defining a new type
// Creating some data
const A = Vec3{ .data = .{1, 2, 3} };
const B = Vec3{ .data = .{4, 5, 6} };
// Computing the dot product
const y = A.dot(B);
// Checking the result
try std.testing.expect(y == 32);
}
The previous test will pass because is a correct program. However, the
following one, which is trying to compute a dot product between a
vector of three elements and another of two elements, will not be even
compiled. It is an incorrect program because is mixing vectors of different
size in the dot product: Vec3 and Vec2.
test "Vectors of different size" {
const Vec3 = Vector(3, f64); // Vector of size 3
const Vec2 = Vector(2, f64); // Vector of size 2
const A = Vec3{ .data = .{1.0, 2.0, 3.0} };
const B = Vec2{ .data = .{4.0, 5.0} };
_ = A.dot(B); // Invalid operation
}
This is the error reported by the Zig compiler:
vector.zig:29:15: error: expected type 'vector.Vector(3,f64)', found
'vector.Vector(2,f64)'
_ = A.dot(B);
^
vector.zig:4:12: note: struct declared here (2 times)
return struct {
^~~~~~
vector.zig:6:42: note: parameter type declared here
pub fn dot(self: @This(), other: @This()) T {
In conclusion, comptime is a powerful feature in Zig that enables a
type-driven development approach by allowing developers to generate,
validate, or manipulate types at compile time using regular code.
The type-function pattern provides a fundamental mechanism for implementing
generics in Zig, enabling the creation of correct and efficient data
structures. With comptime, developers can write more expressive and
concise code, leading to improved productivity and maintainability.