A function in Zig can take types as parameters.
This is the basis of generic programming in the language. Instead of writing separate functions for integers, floating-point values, or user-defined types, a single function can operate on many kinds of values.
Here is a simple example:
const std = @import("std");
fn add(comptime T: type, a: T, b: T) T {
return a + b;
}
pub fn main() void {
const x = add(i32, 3, 4);
const y = add(f64, 1.5, 2.25);
std.debug.print("{d}\n", .{x});
std.debug.print("{d}\n", .{y});
}The output is:
7
3.75The first parameter is unusual:
comptime T: typeT is not a normal runtime value. It is known at compile time.
The type of T is:
typeIn Zig, types themselves are values available during compilation.
The call:
add(i32, 3, 4)passes the integer type i32 into the function.
Inside the function, the parameters become:
a: i32
b: i32and the return type becomes:
i32The compiler generates a specialized version of the function for that type.
The second call:
add(f64, 1.5, 2.25)creates another specialization using f64.
The function body is written once:
return a + b;but the generated machine code depends on the concrete type.
A generic function may also infer types from arguments.
This version does not require the caller to pass T explicitly:
const std = @import("std");
fn max(a: anytype, b: anytype) @TypeOf(a, b) {
return if (a > b) a else b;
}
pub fn main() void {
const x = max(10, 20);
const y = max(3.5, 1.25);
std.debug.print("{d}\n", .{x});
std.debug.print("{d}\n", .{y});
}The output is:
20
3.5anytype means the compiler should infer the parameter type from the argument.
The return type is:
@TypeOf(a, b)This builtin asks the compiler to compute a common type from the arguments.
Generic functions are compiled lazily. Zig does not generate machine code for every possible type. A specialization appears only when the function is used.
This program is legal:
fn square(comptime T: type, x: T) T {
return x * x;
}
pub fn main() void {}Even if square contains invalid operations for some types, no error occurs until the function is instantiated with such a type.
For example:
const std = @import("std");
fn square(comptime T: type, x: T) T {
return x * x;
}
pub fn main() void {
const s = square([]const u8, "zig");
_ = s;
}fails because slices cannot be multiplied.
The compiler reports the error only after the function is used with that type.
Generic programming in Zig is based on ordinary language features:
- compile-time execution
- types as values
- normal function calls
- specialization during compilation
There is no separate template language.
Exercise 11-1. Write a generic min function.
Exercise 11-2. Write a generic swap function using pointers.
Exercise 11-3. Write a generic function that returns the larger of three values.
Exercise 11-4. Modify add so it works only for integer types.