CAPÍTULO 8

Genéricos


Usamos argumentos genéricos cuando queremos crear funciones que puedan aceptar parámetros de distintos tipos. La sintaxis es la siguiente:


fn nombre_func ( T: type, x_a: T , … ,  ) tipo_devuelto //también puede ser T

o

fn nombre_func ( x_a: anytype , … ,  ) tipo_devuelto //también puede ser @TypeOf(x_a)


Este tipo de funciones, en realidad, tienen que ver con cómo el compilador de Zig reestructura el código fuente. Cuando definimos una función genérica y luego la usamos , el compilador la trata como una plantilla para generar una versión adaptada para cada tipo que aparece en las llamadas.

Ambas declaraciones especifican que el argumento x_a puede ser de diferentes tipos, según la llamada. Aún así, en el cuerpo de la función, hemos de tener en cuenta que al final operamos con un tipo concreto.

El inventor, entre otras cosas, estaba trabajando en una máquina voladora y en un arma nueva para el rey. La máquina voladora -por el riesgo que suponía probarla- seguía a medio hacer, pero el arma funcionaba casi a la perfección: al girar una manivela y soltarla, disparaba decenas de puntas de flecha. Eso sí, no paraba hasta agotar la carga.

load_ammo.zig

const std = @import("std");

const print = std.debug.print;

// puntas de flecha de distintos materiales

const Proyectile = enum {

    Wood,Iron,Steel,

    Copper,Rock,

};

// la ametralladora primitiva

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);

    }

    // cargar diferentes cantidades / tipos de munición

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

        // función anónima solo accesible desde este 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 hay más sitio\n", .{})

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

                    o_me.l_ammo.appendAssumeCapacity(x_bullet)

                else

                    print("¡No puede disparar eso!\n", .{});

            }

        }._;

        // si es un array cargamos todos los elementos

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

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

          else => load_single(self, x_ammo),

        }

    }

    // dispara todos los proyectiles

    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();

    // cargar un proyectil

    o_mgun.load_ammo(Proyectile.Iron);

    // cargar algo que se encuentra en el suelo

    o_mgun.load_ammo("Algo que estaba en el suelo");

    // cargar una banda de proyectiles

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

    // disparar

    o_mgun.shoot();

}

$ zig run load_ammo.zig

zig run load_ammo.zig

¡No puede disparar eso!

.Steel

.Rock

.Copper

.Iron

En este ejemplo vemos cómo, en la declaración de la función load_ammo, usamos x_ammo: anytype:

  // cargar diferentes cantidades / tipos de munición

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

Hacemos uso de genéricos porque queremos permitir que se pueda añadir un único proyectil o varios a la vez representados por un array.  También, podríamos definir de manera explícita el tipo como T y usarlo así:

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

Dentro del cuerpo de la función es necesario comprobar el tipo de dato concreto que estamos recibiendo para poder operar correctamente. Para ello usamos la función @TypeOf - para obtener el tipo del valor y @typeInfo - para inspeccionar la estructura y saber si se trata de un array.

  // si es un array cargamos todos los elementos

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

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

    else => load_single(self, x_ammo),

  }

Puedes encontrar información detallada sobre estas funciones puedes en https://ziglang.org/documentation/master/#Builtin-Functions o en el propio código fuente de Zig, en el fichero builtin.zig. Verás que @typeInfo devuelve un union:

  pub const Type = union(enum) {

    type: void,

    void: void,

    bool: void,

    noreturn: void,

    int: Int,

    float: Float,

    pointer: Pointer,

    array: Array,

    ...

En el capítulo 6 vimos que en un union solo un campo puede estar activo a la vez. Aquí detectamos si el campo activo es .array, iteramos sobre los datos que nos llegan y “cargamos” un proyectil por iteración.

Funciones que devuelven errores
Funciones Anónimas y Encapsuladas
© 2025 Zen of Zig