← Volver al listado de tecnologías

Comptime en Zig

Por: Artiko
zigcomptimegenericsmetaprogramacion

Comptime

Conceptos Básicos

Comptime permite ejecutar código en tiempo de compilación:

const std = @import("std");

// Constante evaluada en comptime
const TABLA_CUADRADOS = blk: {
    var tabla: [10]i32 = undefined;
    for (0..10) |i| {
        tabla[i] = @as(i32, @intCast(i * i));
    }
    break :blk tabla;
};

pub fn main() void {
    // La tabla ya está calculada en compilación
    std.debug.print("5² = {d}\n", .{TABLA_CUADRADOS[5]}); // 25

    // Comptime inline
    const resultado = comptime blk: {
        var suma: i32 = 0;
        for (1..101) |i| {
            suma += @as(i32, @intCast(i));
        }
        break :blk suma;
    };
    std.debug.print("Suma 1-100: {d}\n", .{resultado}); // 5050
}

Funciones Genéricas

const std = @import("std");

fn maximo(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

fn intercambiar(comptime T: type, a: *T, b: *T) void {
    const temp = a.*;
    a.* = b.*;
    b.* = temp;
}

fn crearArray(comptime T: type, comptime N: usize, valor: T) [N]T {
    return [_]T{valor} ** N;
}

pub fn main() void {
    std.debug.print("Max i32: {d}\n", .{maximo(i32, 5, 10)});
    std.debug.print("Max f64: {d}\n", .{maximo(f64, 3.14, 2.71)});

    var x: i32 = 5;
    var y: i32 = 10;
    intercambiar(i32, &x, &y);
    std.debug.print("x={d}, y={d}\n", .{ x, y });

    const arr = crearArray(u8, 5, 42);
    std.debug.print("Array: {any}\n", .{arr});
}

Type Reflection

const std = @import("std");

fn imprimirInfo(comptime T: type) void {
    const info = @typeInfo(T);

    std.debug.print("Tipo: {s}\n", .{@typeName(T)});
    std.debug.print("Tamaño: {d} bytes\n", .{@sizeOf(T)});
    std.debug.print("Alineación: {d}\n", .{@alignOf(T)});

    switch (info) {
        .Int => |int_info| {
            std.debug.print("  Bits: {d}\n", .{int_info.bits});
            std.debug.print("  Signedness: {s}\n", .{@tagName(int_info.signedness)});
        },
        .Struct => |struct_info| {
            std.debug.print("  Campos: {d}\n", .{struct_info.fields.len});
            for (struct_info.fields) |field| {
                std.debug.print("    - {s}: {s}\n", .{ field.name, @typeName(field.type) });
            }
        },
        else => {},
    }
}

const Persona = struct {
    nombre: []const u8,
    edad: u32,
    activo: bool,
};

pub fn main() void {
    imprimirInfo(i32);
    std.debug.print("\n", .{});
    imprimirInfo(Persona);
}

Generación de Código

const std = @import("std");

fn GenerarVector(comptime T: type, comptime N: usize) type {
    return struct {
        datos: [N]T,

        const Self = @This();

        pub fn init(valor: T) Self {
            return .{ .datos = [_]T{valor} ** N };
        }

        pub fn get(self: Self, index: usize) T {
            return self.datos[index];
        }

        pub fn set(self: *Self, index: usize, valor: T) void {
            self.datos[index] = valor;
        }

        pub fn sumar(self: Self, otro: Self) Self {
            var resultado: Self = undefined;
            inline for (0..N) |i| {
                resultado.datos[i] = self.datos[i] + otro.datos[i];
            }
            return resultado;
        }

        pub fn len() usize {
            return N;
        }
    };
}

pub fn main() void {
    const Vec3 = GenerarVector(f32, 3);

    var v1 = Vec3.init(1.0);
    var v2 = Vec3.init(2.0);

    v1.set(0, 10.0);
    const v3 = v1.sumar(v2);

    std.debug.print("v3[0] = {d}\n", .{v3.get(0)}); // 12.0
    std.debug.print("Longitud: {d}\n", .{Vec3.len()});
}

Inline Loops

const std = @import("std");

fn sumarTupla(comptime N: usize, tupla: [N]i32) i32 {
    var suma: i32 = 0;
    // Loop desenrollado en compilación
    inline for (tupla) |valor| {
        suma += valor;
    }
    return suma;
}

fn aplicarATodos(comptime N: usize, arr: *[N]i32, comptime f: fn (i32) i32) void {
    inline for (arr) |*elem| {
        elem.* = f(elem.*);
    }
}

fn duplicar(x: i32) i32 {
    return x * 2;
}

pub fn main() void {
    const tupla = [_]i32{ 1, 2, 3, 4, 5 };
    std.debug.print("Suma: {d}\n", .{sumarTupla(5, tupla)});

    var arr = [_]i32{ 1, 2, 3 };
    aplicarATodos(3, &arr, duplicar);
    std.debug.print("Duplicados: {any}\n", .{arr});
}

Comptime Strings

const std = @import("std");

fn formatearComptime(comptime fmt: []const u8, comptime args: anytype) []const u8 {
    comptime {
        var buf: [1024]u8 = undefined;
        const resultado = std.fmt.bufPrint(&buf, fmt, args) catch unreachable;
        var final: [resultado.len]u8 = undefined;
        @memcpy(&final, resultado);
        return &final;
    }
}

fn generarNombreCampo(comptime prefix: []const u8, comptime index: usize) []const u8 {
    return prefix ++ std.fmt.comptimePrint("{d}", .{index});
}

pub fn main() void {
    const mensaje = comptime formatearComptime("Valor: {d}", .{42});
    std.debug.print("{s}\n", .{mensaje});

    const campo = comptime generarNombreCampo("campo_", 5);
    std.debug.print("Campo: {s}\n", .{campo}); // campo_5
}

Testing de Comptime

const std = @import("std");
const testing = std.testing;

fn factorial(comptime n: u64) u64 {
    if (n == 0) return 1;
    return n * factorial(n - 1);
}

fn fibonacci(comptime n: usize) usize {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

fn esPrimo(comptime n: u64) bool {
    if (n < 2) return false;
    if (n == 2) return true;
    if (n % 2 == 0) return false;

    var i: u64 = 3;
    while (i * i <= n) : (i += 2) {
        if (n % i == 0) return false;
    }
    return true;
}

fn maximoGenerico(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}

fn ContadorGenerico(comptime T: type) type {
    return struct {
        valor: T,

        const Self = @This();

        pub fn init() Self {
            return .{ .valor = 0 };
        }

        pub fn incrementar(self: *Self) void {
            self.valor += 1;
        }

        pub fn obtener(self: Self) T {
            return self.valor;
        }
    };
}

test "factorial comptime" {
    const f5 = comptime factorial(5);
    try testing.expectEqual(@as(u64, 120), f5);

    const f10 = comptime factorial(10);
    try testing.expectEqual(@as(u64, 3628800), f10);
}

test "fibonacci comptime" {
    const fib10 = comptime fibonacci(10);
    try testing.expectEqual(@as(usize, 55), fib10);
}

test "es primo comptime" {
    try testing.expect(comptime esPrimo(2));
    try testing.expect(comptime esPrimo(17));
    try testing.expect(comptime esPrimo(97));
    try testing.expect(!comptime esPrimo(4));
    try testing.expect(!comptime esPrimo(100));
}

test "maximo genérico con diferentes tipos" {
    try testing.expectEqual(@as(i32, 10), maximoGenerico(i32, 5, 10));
    try testing.expectEqual(@as(f64, 3.14), maximoGenerico(f64, 2.71, 3.14));
    try testing.expectEqual(@as(u8, 255), maximoGenerico(u8, 100, 255));
}

test "tipo generado" {
    const ContadorI32 = ContadorGenerico(i32);
    var contador = ContadorI32.init();

    try testing.expectEqual(@as(i32, 0), contador.obtener());

    contador.incrementar();
    contador.incrementar();
    contador.incrementar();

    try testing.expectEqual(@as(i32, 3), contador.obtener());
}

test "array generado en comptime" {
    const TABLA = comptime blk: {
        var t: [5]i32 = undefined;
        for (0..5) |i| {
            t[i] = @as(i32, @intCast(i * i));
        }
        break :blk t;
    };

    try testing.expectEqual(@as(i32, 0), TABLA[0]);
    try testing.expectEqual(@as(i32, 1), TABLA[1]);
    try testing.expectEqual(@as(i32, 4), TABLA[2]);
    try testing.expectEqual(@as(i32, 16), TABLA[4]);
}

test "typeInfo" {
    const info = @typeInfo(i32);
    try testing.expect(info == .Int);
    try testing.expectEqual(@as(u16, 32), info.Int.bits);
}

Ejercicios

  1. Crea una tabla de senos precalculada en comptime
  2. Implementa un tipo Matrix(T, rows, cols) genérico con operaciones
  3. Genera código para serializar structs automáticamente

Resumen