A generic function is a function that works with many types instead of only one type.
Generic Functions
A generic function is a function that works with many types instead of only one type.
Without generics, you often duplicate code.
Example:
fn addI32(a: i32, b: i32) i32 {
return a + b;
}Then later:
fn addF64(a: f64, b: f64) f64 {
return a + b;
}The logic is identical.
Only the types change.
Generic functions solve this problem.
Zig Generics Use comptime
Zig implements generics through compile-time programming.
The most common pattern uses:
comptimeExample:
fn add(
comptime T: type,
a: T,
b: T,
) T {
return a + b;
}This function works with many types.
Calling Generic Functions
Example:
const x = add(i32, 10, 20);
const y = add(f64, 1.5, 2.5);Results:
x = 30
y = 4.0The compiler generates specialized versions for each type.
Conceptually:
add(i32, ...)
-> integer version
add(f64, ...)
-> floating-point versionUnderstanding comptime T: type
This line:
comptime T: typemeans:
Tis known at compile timeTitself is a type
The caller passes a type as input.
Example:
add(i32, 1, 2)Here:
T = i32The compiler creates a version specialized for i32.
Why Zig Uses Compile-Time Generics
Many languages implement generics differently.
Examples:
| Language | Generic System |
|---|---|
| C++ | templates |
| Rust | monomorphization + traits |
| Java | type erasure |
| Go | type parameters |
| Zig | compile-time execution |
Zig’s system is unusually flexible because generics are built from normal compile-time language features.
A Generic Maximum Function
Example:
fn max(
comptime T: type,
a: T,
b: T,
) T {
return if (a > b) a else b;
}Calling:
const a = max(i32, 10, 20);
const b = max(f64, 1.5, 0.5);Results:
a = 20
b = 1.5Generic Arrays
Generics work well with arrays.
Example:
fn first(
comptime T: type,
values: []const T,
) T {
return values[0];
}Calling:
const ints = [_]i32{ 1, 2, 3 };
const floats = [_]f64{ 1.5, 2.5 };
const a = first(i32, &ints);
const b = first(f64, &floats);The same logic works for many element types.
Generic Structs
Generics are not limited to functions.
Structs can also be generic.
Example:
fn Box(comptime T: type) type {
return struct {
value: T,
};
}Using it:
const IntBox = Box(i32);
const box = IntBox{
.value = 123,
};This generates a custom structure specialized for i32.
Generic Data Structures
Generic structs are extremely important.
Examples:
- lists
- hash maps
- queues
- trees
- allocators
- buffers
The Zig standard library uses generics heavily.
Generic Printing
Example:
fn printValue(value: anytype) void {
std.debug.print("{}\n", .{value});
}This uses:
anytypewhich allows automatic generic parameter inference.
Calling:
printValue(123);
printValue(3.14);
printValue(true);The compiler determines the types automatically.
anytype
anytype is a convenient shorthand.
Instead of:
fn print(
comptime T: type,
value: T,
)you can write:
fn print(value: anytype)This is common in Zig APIs.
Type Inference
Example:
fn identity(value: anytype) @TypeOf(value) {
return value;
}Calling:
const x = identity(10);
const y = identity(3.5);The compiler infers types automatically.
Compile-Time Specialization
Each type creates a specialized version.
Conceptually:
add(i32, ...)
-> generated code #1
add(f64, ...)
-> generated code #2This often produces very efficient machine code.
Generic Constraints
Sometimes not all types are valid.
Example:
fn add(
comptime T: type,
a: T,
b: T,
) T {
return a + b;
}This works for numeric types.
But calling:
add([]const u8, "a", "b");fails because strings cannot use + this way.
The compiler checks operations during specialization.
Compile-Time Type Inspection
Zig can inspect types at compile time.
Example:
fn describe(
comptime T: type,
) void {
std.debug.print(
"{any}\n",
.{@typeInfo(T)},
);
}Calling:
describe(i32);This is part of Zig’s reflection system.
Generic Algorithms
Generic algorithms are one of the biggest uses of generics.
Example conceptual algorithms:
- sorting
- searching
- hashing
- parsing
- serialization
One algorithm can support many types.
Generic Memory Utilities
Example conceptual function:
fn swap(
comptime T: type,
a: *T,
b: *T,
) void {
}This can swap:
- integers
- floats
- structs
- arrays
without duplicating code.
Generics and Zero-Cost Abstractions
Zig generics are designed for high performance.
The compiler generates specialized code directly.
This avoids many runtime costs.
Conceptually:
generic abstraction
->
specialized machine codeThis is often called a zero-cost abstraction.
Generic Iteration Example
fn printAll(values: anytype) void {
for (values) |value| {
std.debug.print(
"{}\n",
.{value},
);
}
}Calling:
const numbers = [_]i32{ 1, 2, 3 };
printAll(numbers);The compiler adapts the function automatically.
Generic Errors
Generic code can produce confusing compiler messages initially.
Example:
fn test(value: anytype) void {
value.invalidMethod();
}Compiler errors may appear during specialization instead of initial parsing.
This is normal in compile-time generic systems.
Generics vs Runtime Polymorphism
Generics:
compile-time specializationFunction pointers/interfaces:
runtime polymorphismGenerics usually produce faster code because decisions happen during compilation.
Runtime polymorphism is more dynamic but may cost more at runtime.
A Complete Example
const std = @import("std");
fn swap(
comptime T: type,
a: *T,
b: *T,
) void {
const temp = a.*;
a.* = b.*;
b.* = temp;
}
pub fn main() void {
var x: i32 = 10;
var y: i32 = 20;
swap(i32, &x, &y);
std.debug.print(
"{} {}\n",
.{ x, y },
);
var a: f64 = 1.5;
var b: f64 = 2.5;
swap(f64, &a, &b);
std.debug.print(
"{} {}\n",
.{ a, b },
);
}Output:
20 10
2.5 1.5One function works for multiple types safely and efficiently.
Mental Model
Generic functions mean:
write logic once
use it with many typesZig achieves this through compile-time specialization.
The core ideas are:
| Feature | Purpose |
|---|---|
comptime | compile-time parameters |
type | types as values |
anytype | inferred generic parameters |
| specialization | generated type-specific code |
Generics are one of Zig’s most powerful features because they combine:
- flexibility
- strong type safety
- compile-time checking
- high performance
without requiring a separate template language or heavy runtime system.