Ir al contenido principal

Expresiones switch en Zig

Las expresiones switch en Zig son una herramienta fundamental para controlar el flujo de nuestro programa en función de diferentes valores. A diferencia de otros lenguajes donde el switch es una sentencia, en Zig es una expresión, lo que significa que devuelve un valor y puede usarse dentro de otras expresiones o asignaciones. En este artículo exploraremos en profundidad cómo funcionan las expresiones switch en Zig, sus características especiales y los diversos casos de uso que podemos encontrar.

Fundamentos de las expresiones switch

Una expresión switch en Zig evalúa un valor y ejecuta código diferente dependiendo de qué caso coincida con ese valor. La sintaxis básica es:

const resultado = switch (valor) {
    caso1 => expresión1,
    caso2 => expresión2,
    // ... más casos
    else => expresiónPorDefecto,
};

Veamos un ejemplo sencillo:

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

test "switch básico" {
    const a: u64 = 10;
    
    const b = switch (a) {
        1, 2, 3 => 0,       // Múltiples casos combinados
        5...100 => 1,       // Rango de valores (inclusivo)
        101 => blk: {       // Bloque de código complejo
            const c: u64 = 5;
            break :blk c * 2 + 1;
        },
        else => 9,          // Caso por defecto
    };
    
    try expect(b == 1);     // Como a=10, coincide con el rango 5...100
}

Características importantes:

  1. No hay cascada (fallthrough): A diferencia de C o Java, en Zig los casos no continúan al siguiente automáticamente.
  2. Todos los caminos deben devolver el mismo tipo: Todas las ramas deben poder convertirse a un tipo común.
  3. La cláusula else es obligatoria a menos que se cubran explícitamente todos los posibles valores del tipo evaluado.
  4. Uso de rangos con ...: Permite especificar un rango inclusivo de valores para un caso.
  5. Combinación de casos con ,: Puedes listar múltiples valores para un mismo caso.

Evaluación en tiempo de compilación

Si la expresión del switch se conoce en tiempo de compilación, el compilador evaluará el switch durante la compilación. Esto permite escribir código que se comporta diferente según la plataforma o configuración:

const builtin = @import("builtin");

const mensaje_sistema = switch (builtin.target.os.tag) {
    .linux => "Estamos en Linux",
    .windows => "Estamos en Windows",
    .macos => "Estamos en macOS",
    else => "Sistema operativo desconocido",
};

test "switch en tiempo de compilación" {
    // El mensaje se determina durante la compilación
    // según el sistema operativo de destino
    _ = mensaje_sistema;
}

Switch con uniones etiquetadas (Tagged Unions)

Una de las características más poderosas de switch en Zig es su capacidad para extraer y usar valores de uniones etiquetadas:

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

test "switch con unión etiquetada" {
    const Punto = struct {
        x: u8,
        y: u8,
    };
    const Item = union(enum) {
        entero: u32,
        punto: Punto,
        nada,
        texto: u32,
    };

    var elemento = Item{ .punto = Punto{ .x = 1, .y = 2 } };

    const resultado = switch (elemento) {
        // Podemos capturar el valor con una variable
        Item.entero, Item.texto => |valor| valor,

        // Podemos obtener una referencia al valor con *
        Item.punto => |*p| blk: {
            p.*.x += 1;  // Modificamos x a través del puntero
            break :blk 6;
        },

        // No hace falta else porque cubrimos todos los casos
        Item.nada => 8,
    };

    try expect(resultado == 6);
    try expect(elemento.punto.x == 2);  // La modificación tuvo efecto
}

En este ejemplo:

  • Definimos una unión etiquetada Item
  • Usamos |valor| para capturar el valor dentro de la unión
  • Usamos |*p| para obtener un puntero al valor y modificarlo

Switch con capturas de patrones

En Zig, switch permite capturar valores de patrones, lo que facilita trabajar con estructuras de datos complejas:

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

test "switch con capturas" {
    const Valor = union(enum) {
        entero: i32,
        booleano: bool,
        flotante: f32,
    };

    const valor: Valor = .{ .entero = 42 };

    const descripcion = switch (valor) {
        .entero => |i| if (i > 0) "entero positivo" else "entero no positivo",
        .booleano => |b| if (b) "verdadero" else "falso",
        .flotante => |f| if (f > 0) "flotante positivo" else "flotante no positivo",
    };

    try expect(std.mem.eql(u8, descripcion, "entero positivo"));
}

Switch con literales de enumeración

Para simplificar el código, Zig permite usar literales de enumeración en el switch, evitando repetir el tipo de enumeración:

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

test "switch con literales de enumeración" {
    const Color = enum {
        rojo,
        verde,
        azul,
    };

    const color = Color.verde;
    
    const valor = switch (color) {
        .rojo => 0xFF0000,
        .verde => 0x00FF00,
        .azul => 0x0000FF,
    };

    try expect(valor == 0x00FF00);
}

Exahustividad en las expresiones switch

Una característica importante de Zig es que exige que el switch sea exhaustivo: debe manejar todos los posibles valores del tipo que se está evaluando. Si no se usa else, todos los valores posibles deben estar cubiertos:

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

test "switch exhaustivo" {
    const Estado = enum {
        inactivo,
        activo,
        bloqueado,
    };

    const estado = Estado.activo;
    
    const mensaje = switch (estado) {
        .inactivo => "El sistema está inactivo",
        .activo => "El sistema está activo",
        .bloqueado => "El sistema está bloqueado",
        // No hace falta else porque ya cubrimos todos los casos
    };
    
    try expect(std.mem.eql(u8, mensaje, "El sistema está activo"));
}

Si añadimos un nuevo valor a la enumeración Estado y no actualizamos todos los switch para manejarlo, el compilador nos dará un error. Esto ayuda a evitar errores cuando modificamos nuestros tipos.

Conclusión

Las expresiones switch en Zig son extremadamente potentes y flexibles. Su diseño evita muchos de los problemas de seguridad asociados con las sentencias switch en otros lenguajes, como la verificación en cascada de todas las ramas (fallthrough) o la falta de exhaustividad.

Recapitulando las características clave:

  • Son expresiones que devuelven un valor.
  • No permiten fallthrough (cascada) entre casos.
  • Requieren exhaustividad (manejar todos los posibles valores).
  • Permiten capturar y modificar valores de uniones etiquetadas.
  • Soportan evaluación en tiempo de compilación.

Estas características hacen que el switch en Zig sea una herramienta indispensable para escribir código robusto, seguro y expresivo. Al aprovechar al máximo estas capacidades, podrás crear código que sea no solo eficiente, sino también claro y fácil de mantener.