← Volver al listado de tecnologías

Structs y Enums en Zig

Por: Artiko
zigstructsenumsunionstipos

Structs y Enums

Structs Básicos

const std = @import("std");

const Punto = struct {
    x: f64,
    y: f64,
};

const Rectangulo = struct {
    origen: Punto,
    ancho: f64,
    alto: f64,

    // Método
    pub fn area(self: Rectangulo) f64 {
        return self.ancho * self.alto;
    }

    // Método que modifica
    pub fn escalar(self: *Rectangulo, factor: f64) void {
        self.ancho *= factor;
        self.alto *= factor;
    }
};

pub fn main() void {
    // Crear instancia
    const p = Punto{ .x = 10.0, .y = 20.0 };

    // Con valores por defecto
    var rect = Rectangulo{
        .origen = .{ .x = 0, .y = 0 },
        .ancho = 100,
        .alto = 50,
    };

    std.debug.print("Área: {d}\n", .{rect.area()});

    rect.escalar(2);
    std.debug.print("Nueva área: {d}\n", .{rect.area()});
}

Valores por Defecto

const Config = struct {
    puerto: u16 = 8080,
    host: []const u8 = "localhost",
    max_conexiones: u32 = 100,
    debug: bool = false,
};

pub fn main() void {
    // Usar todos los defaults
    const config1 = Config{};

    // Sobrescribir algunos
    const config2 = Config{
        .puerto = 3000,
        .debug = true,
    };

    std.debug.print("Puerto: {d}\n", .{config2.puerto});
}

Structs Anónimos

const std = @import("std");

fn crearPunto(x: f64, y: f64) struct { x: f64, y: f64 } {
    return .{ .x = x, .y = y };
}

pub fn main() void {
    const p = crearPunto(5.0, 10.0);
    std.debug.print("Punto: ({d}, {d})\n", .{ p.x, p.y });

    // Tuple (struct sin nombres)
    const tupla: struct { i32, []const u8 } = .{ 42, "hola" };
    std.debug.print("Valor: {d}, Texto: {s}\n", .{ tupla[0], tupla[1] });
}

Enums

const std = @import("std");

const Color = enum {
    rojo,
    verde,
    azul,

    pub fn esCalido(self: Color) bool {
        return self == .rojo;
    }
};

const DiaSemana = enum(u8) {
    lunes = 1,
    martes = 2,
    miercoles = 3,
    jueves = 4,
    viernes = 5,
    sabado = 6,
    domingo = 7,

    pub fn esFinDeSemana(self: DiaSemana) bool {
        return self == .sabado or self == .domingo;
    }
};

pub fn main() void {
    const color = Color.verde;
    const dia = DiaSemana.sabado;

    std.debug.print("Es cálido: {}\n", .{color.esCalido()});
    std.debug.print("Es fin de semana: {}\n", .{dia.esFinDeSemana()});

    // Convertir a entero
    const valor: u8 = @intFromEnum(dia);
    std.debug.print("Valor numérico: {d}\n", .{valor});
}

Tagged Unions

const std = @import("std");

const Resultado = union(enum) {
    exito: i32,
    error_mensaje: []const u8,
    pendiente,

    pub fn valor(self: Resultado) ?i32 {
        return switch (self) {
            .exito => |v| v,
            else => null,
        };
    }
};

const Forma = union(enum) {
    circulo: f64, // radio
    rectangulo: struct { ancho: f64, alto: f64 },
    triangulo: struct { base: f64, altura: f64 },

    pub fn area(self: Forma) f64 {
        return switch (self) {
            .circulo => |radio| std.math.pi * radio * radio,
            .rectangulo => |r| r.ancho * r.alto,
            .triangulo => |t| (t.base * t.altura) / 2,
        };
    }
};

pub fn main() void {
    const r1 = Resultado{ .exito = 42 };
    const r2 = Resultado{ .error_mensaje = "Algo salió mal" };

    if (r1.valor()) |v| {
        std.debug.print("Valor: {d}\n", .{v});
    }

    const circulo = Forma{ .circulo = 5.0 };
    const rect = Forma{ .rectangulo = .{ .ancho = 10, .alto = 5 } };

    std.debug.print("Área círculo: {d}\n", .{circulo.area()});
    std.debug.print("Área rectángulo: {d}\n", .{rect.area()});
}

Packed Structs

Para control preciso del layout de memoria:

const std = @import("std");

const Flags = packed struct {
    activo: bool,
    visible: bool,
    seleccionado: bool,
    _padding: u5 = 0,
};

pub fn main() void {
    var flags = Flags{
        .activo = true,
        .visible = false,
        .seleccionado = true,
    };

    std.debug.print("Tamaño: {d} bytes\n", .{@sizeOf(Flags)}); // 1 byte

    // Convertir a entero
    const como_byte: u8 = @bitCast(flags);
    std.debug.print("Como byte: {b}\n", .{como_byte});
}

Testing de Structs y Enums

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

const Punto = struct {
    x: f64,
    y: f64,

    pub fn distancia(self: Punto, otro: Punto) f64 {
        const dx = self.x - otro.x;
        const dy = self.y - otro.y;
        return @sqrt(dx * dx + dy * dy);
    }

    pub fn mover(self: *Punto, dx: f64, dy: f64) void {
        self.x += dx;
        self.y += dy;
    }
};

const Estado = enum {
    activo,
    inactivo,
    pendiente,

    pub fn esTerminal(self: Estado) bool {
        return self == .activo or self == .inactivo;
    }
};

const Respuesta = union(enum) {
    ok: i32,
    err: []const u8,

    pub fn esOk(self: Respuesta) bool {
        return switch (self) {
            .ok => true,
            .err => false,
        };
    }

    pub fn obtenerValor(self: Respuesta) ?i32 {
        return switch (self) {
            .ok => |v| v,
            .err => null,
        };
    }
};

test "crear struct" {
    const p = Punto{ .x = 3.0, .y = 4.0 };
    try testing.expectEqual(@as(f64, 3.0), p.x);
    try testing.expectEqual(@as(f64, 4.0), p.y);
}

test "método de struct" {
    const p1 = Punto{ .x = 0, .y = 0 };
    const p2 = Punto{ .x = 3, .y = 4 };

    try testing.expectEqual(@as(f64, 5.0), p1.distancia(p2));
}

test "modificar struct" {
    var p = Punto{ .x = 0, .y = 0 };
    p.mover(10, 20);

    try testing.expectEqual(@as(f64, 10.0), p.x);
    try testing.expectEqual(@as(f64, 20.0), p.y);
}

test "enum básico" {
    const estado = Estado.activo;
    try testing.expect(estado == .activo);
    try testing.expect(estado.esTerminal());
}

test "enum no terminal" {
    const estado = Estado.pendiente;
    try testing.expect(!estado.esTerminal());
}

test "tagged union ok" {
    const resp = Respuesta{ .ok = 42 };

    try testing.expect(resp.esOk());
    try testing.expectEqual(@as(?i32, 42), resp.obtenerValor());
}

test "tagged union error" {
    const resp = Respuesta{ .err = "fallo" };

    try testing.expect(!resp.esOk());
    try testing.expectEqual(@as(?i32, null), resp.obtenerValor());
}

test "switch exhaustivo en union" {
    const resp = Respuesta{ .ok = 100 };

    const mensaje = switch (resp) {
        .ok => |v| if (v > 50) "grande" else "pequeño",
        .err => "error",
    };

    try testing.expectEqualStrings("grande", mensaje);
}

test "struct con valores default" {
    const Config = struct {
        valor: i32 = 10,
        nombre: []const u8 = "default",
    };

    const c = Config{};
    try testing.expectEqual(@as(i32, 10), c.valor);
    try testing.expectEqualStrings("default", c.nombre);
}

Ejercicios

  1. Crea un struct Persona con nombre, edad y un método esMayorDeEdad
  2. Implementa un enum Operacion con variantes suma, resta, mult, div y un método aplicar
  3. Crea un tagged union Resultado que represente éxito con valor o error con código

Resumen