zig-sentencia-switch

Switch en Zig: patrones, rangos y casos exhaustivos

  • 4 min

Un switch en Zig es una estructura de selección exhaustiva que obliga a contemplar todos los casos posibles. Si vienes de C, C++ o Java, ya conocerás la utilidad de esta estructura y también los errores que puede provocar el fallthrough accidental.

Es útil, sí. Pero también es peligrosa. ¿Cuántas veces se os ha olvidado poner un break y el código se ha ejecutado en cascada (fallthrough) provocando un bug silencioso? ¿O cuántas veces habéis añadido un nuevo valor a un enum y se os ha olvidado actualizar el switch que lo gestionaba?

Zig evita ambos problemas: no admite caída implícita entre ramas y exige que todos los valores posibles queden cubiertos.

Exhaustividad obligatoria

La diferencia fundamental en Zig es que el switch debe ser exhaustivo. Esto significa que estamos obligados a manejar todos y cada uno de los valores posibles de la variable que estamos evaluando.

Si hacemos un switch sobre un entero u8 (que tiene 256 valores posibles) y solo definimos dos casos, el código no compilará.

const numero: u8 = 5;

// ERROR: switch must handle all possibilities
switch (numero) {
    1 => std.debug.print("Uno", .{}),
    2 => std.debug.print("Dos", .{}),
}
Copied!

Para arreglar esto, tenemos dos opciones: cubrir los 256 casos (imposible) o usar la cláusula else para cubrir “el resto”.

switch (numero) {
    1 => std.debug.print("Uno", .{}),
    2 => std.debug.print("Dos", .{}),
    else => std.debug.print("Otro número", .{}),
}
Copied!

Esta característica resulta especialmente útil con enums. Si añades un estado nuevo, el compilador señala los switch exhaustivos que todavía no lo contemplan.

Sintaxis de las ramas

La sintaxis de Zig usa flechas => en lugar de case 1: ... break;. Además, elimina el fallthrough implícito: solo se ejecuta la rama que coincide.

Listas de valores

Podemos agrupar varios valores en una sola línea separándolos por comas. Esto reemplaza al antiguo patrón de apilar case vacíos.

const letra: u8 = 'e';

switch (letra) {
    'a', 'e', 'i', 'o', 'u' => std.debug.print("Es una vocal\n", .{}),
    else => std.debug.print("Es una consonante (o símbolo)\n", .{}),
}
Copied!

Rangos de valores (...)

Zig permite definir rangos inclusivos usando tres puntos .... Resultan útiles para categorizar datos numéricos.

const puntuacion: u8 = 85;

switch (puntuacion) {
    0...49 => std.debug.print("Suspenso\n", .{}),
    50...69 => std.debug.print("Aprobado\n", .{}),
    70...89 => std.debug.print("Notable\n", .{}),
    90...100 => std.debug.print("Sobresaliente\n", .{}),
    else => std.debug.print("Puntuación inválida\n", .{}),
}
Copied!

Fíjate en la diferencia: En los bucles for el rango es 0..N (exclusivo). En el switch el rango es 0...N (inclusivo).

switch como expresión

Al igual que vimos con el if, el switch en Zig es una expresión. Esto significa que puede devolver un valor que asignamos directamente a una variable.

Esto nos permite convertir lógica compleja en asignaciones const limpias e inmutables.

const dia = 3;

const nombre_dia = switch (dia) {
    1 => "Lunes",
    2 => "Martes",
    3 => "Miércoles",
    4 => "Jueves",
    5 => "Viernes",
    6, 7 => "Fin de semana",
    else => "Día inválido",
};

std.debug.print("Hoy es {s}\n", .{nombre_dia});
Copied!

Observa el punto y coma ; al final de la llave de cierre. Es necesario porque estamos terminando una declaración (const ... = ...;).

Captura de valores en else

A veces, en la rama else, queremos saber cuál fue exactamente el valor que nos trajo aquí. Zig nos permite capturar ese valor usando barras verticales | |.

const x: u8 = 42;

switch (x) {
    0, 10, 20 => std.debug.print("Número redondo pequeño\n", .{}),
    else => |n| std.debug.print("Número no soportado: {d}\n", .{n}),
}
Copied!

Uniones etiquetadas

Aunque veremos las estructuras de datos más adelante, el switch es la herramienta principal para trabajar con uniones etiquetadas (similares a los enums con datos de Rust o Swift).

Si tenemos un tipo que puede ser un Entero O un Texto, usamos switch para desempaquetarlo de forma segura:

// (Ejemplo conceptual)
switch (mi_dato) {
    .entero => |valor| std.debug.print("Es un número: {d}", .{valor}),
    .texto => |str| std.debug.print("Es un texto: {s}", .{str}),
}
Copied!

El compilador comprueba qué variante está activa y solo permite acceder al campo capturado por la rama correspondiente.