Generics

25.1 Generic Functions

fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}
_ = max(i32, 3, 7);
_ = max(f64, 1.5, 0.5);

fn sum(values: anytype) @TypeOf(values[0]) {        // duck typing with anytype
    var total: @TypeOf(values[0]) = 0;
    for (values) |v| total += v;
    return total;
}
_ = sum([_]u32{ 1, 2, 3 });

25.2 Generic Types (Functions Returning Types)

Generic types are memoized: Stack(i32) called twice returns the same type.

fn Stack(comptime T: type) type {
    return struct {
        items: std.ArrayList(T) = .empty,
        const Self = @This();

        pub fn push(self: *Self, allocator: std.mem.Allocator, x: T) !void {
            try self.items.append(allocator, x);
        }
        pub fn pop(self: *Self) ?T {
            return self.items.pop();
        }
        pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
            self.items.deinit(allocator);
        }
    };
}

var s: Stack(i32) = .{};
defer s.deinit(allocator);
try s.push(allocator, 10);
_ = s.pop();

fn Pair(comptime A: type, comptime B: type) type {
    return struct { first: A, second: B };
}
const p = Pair(u8, []const u8){ .first = 1, .second = "x" };
_ = p;

25.3 Constraining Generics

There are no traits or interfaces in the type system. Constraints are expressed as comptime checks (@typeInfo, @hasDecl, @hasField, std.meta.hasFn) with good error messages.

fn average(comptime T: type, xs: []const T) T {
    comptime switch (@typeInfo(T)) {
        .float => {},
        else => @compileError("average requires a float type"),
    };
    var total: T = 0;
    for (xs) |x| total += x;
    return total / @as(T, @floatFromInt(xs.len));
}

fn serialize(writer: *std.Io.Writer, value: anytype) !void {
    const T = @TypeOf(value);
    if (!@hasDecl(T, "serialize")) @compileError(@typeName(T) ++ " has no serialize method");
    try value.serialize(writer);
}