for Zig 0.15.2 Buy

CHAPTER 8

Generics


We use generic arguments when we want to create functions that can accept parameters of different types. The syntax is as follows:


fn function_name ( T: type, x_a: T , … ,  ) return_type //can also be T

or

fn function_name ( x_a: anytype , … ,  ) return_type //can also be @TypeOf(x_a)


These kinds of functions are really about how the Zig compiler restructures the source code. When we define a generic function and then use it, the compiler treats it as a template that generates a version tailored to each type that appears in the calls.

Both declarations specify that the argument x_a can be of different types, depending on the call. Still, in the body of the function, we have to keep in mind that in the end, we're working with a specific type.

The parameters type and anytype are implicitly comptime. If they weren't, the compiler wouldn't be able to generate versions of the same function at compile time. You can write the word comptime in front of them, but you don't need to-though you'll sometimes see it written that way in some tutorials.

Only when the arguments are not type or anytype do you need to explicitly use the comptime keyword in the declarations, and the compiler will warn you if you don't.

The inventor, among other things, was working on a flying machine and a new weapon for the king. The flying machine, due to the risk involved in testing it, was still half-finished, but the weapon worked almost perfectly: by turning a crank and releasing it, it would fire dozens of arrowheads. One catch: it wouldn’t stop until the load was completely spent.

load_ammo.zig

const std = @import("std");

const print = std.debug.print;

// arrowheads made of different materials

const Proyectile = enum {

    Wood,Iron,Steel,

    Copper,Rock,

};

// the primitive machine gun

const MachineGun = struct {

    a_magazine: [100]Proyectile = undefined,

    l_ammo: std.ArrayListUnmanaged(Proyectile) = .{},

    fn init(self: *MachineGun) void {

        self.l_ammo = std.ArrayListUnmanaged(Proyectile).initBuffer(&self.a_magazine);

    }

    // load different amounts/types of ammo

    fn load_ammo(self: *MachineGun, x_ammo: anytype) void {

        // anonymous function only accessible within this scope

        const load_single = struct {

            fn _(o_me: *MachineGun, x_bullet: anytype) void {

                if (o_me.l_ammo.capacity == o_me.l_ammo.items.len)

                    print("No more room\n", .{})

                else if (@TypeOf(x_bullet) == Proyectile)

                    o_me.l_ammo.appendAssumeCapacity(x_bullet)

                else

                    print("Can't shoot that!\n", .{});

            }

        }._;

        // if it's an array, load all elements

        switch (@typeInfo(@TypeOf(x_ammo))) {

          .array => for (x_ammo) |o_bullet| load_single(self, o_bullet),

          else => load_single(self, x_ammo),

        }

    }

    // fire all projectiles

    fn shoot(self: *MachineGun) void {

        while (self.l_ammo.items.len > 0) {

            const o_bullet = self.l_ammo.pop();

            print("{any}\n", .{o_bullet});

        }

    }

};

pub fn main() !void {

    var o_mgun = MachineGun{};

    o_mgun.init();

    // load a single projectile

    o_mgun.load_ammo(Proyectile.Iron);

    // load something found on the ground

    o_mgun.load_ammo("Something found on the ground");

    // load a belt of projectiles

    o_mgun.load_ammo([_]Proyectile{ .Copper, .Rock, .Steel });

    // fire

    o_mgun.shoot();

}

$ zig run load_ammo.zig

zig run load_ammo.zig

Can't shoot that!

.Steel

.Rock

.Copper

.Iron

Chapter summary
Introspection Builtins
© 2025 - 2026 Zen of Zig