zig-bucles-while-for

Bucles en Zig: while, for y control de iteración seguro

  • 4 min

Los bucles en Zig son estructuras para repetir código. Si vienes de C, Java o JavaScript, probablemente estás acostumbrado a que for sea una herramienta multiusos; en Zig esa responsabilidad está más repartida.

En Zig, las cosas están más compartimentadas para mejorar la claridad:

  1. while: Se usa cuando la repetición depende de una condición booleana o queremos un bucle de estilo C.
  2. for: se usa para iterar sobre secuencias (arrays, slices, tuplas o vectores) y rangos de números.

El bucle while

La forma más básica del while es idéntica a la mayoría de lenguajes. Se repite mientras la condición sea true.

var i: u8 = 0;
while (i < 5) {
    std.debug.print("Contando: {d}\n", .{i});
    i += 1;
}
Copied!

La expresión de continuación (:)

Aquí es donde Zig mejora el diseño clásico. En un bucle while complejo, a menudo tenemos que incrementar un contador o avanzar un iterador.

Si usamos la sentencia continue dentro del bucle para saltar a la siguiente iteración, corremos el riesgo de saltarnos también la línea que incrementa el contador, creando un bucle infinito.

Zig soluciona esto permitiendo definir la expresión de actualización como parte de la cabecera del bucle, separada por dos puntos :.

var i: u8 = 0;

// while (condicion) : (accion_al_terminar_vuelta)
while (i < 10) : (i += 1) {
    if (i == 5) continue; // Salta, pero EJECUTA el i += 1
    std.debug.print("{d} ", .{i});
}
Copied!

Esto equivale al for (int i = 0; i < 10; i++) de C, pero mantiene la semántica de “mientras”.

El bucle for

En Zig, el bucle for es fundamentalmente un “for-each”. No se usa para condiciones arbitrarias, sino para recorrer cosas que tienen un principio y un fin conocidos.

Iterar sobre arrays y slices

La sintaxis utiliza las barras verticales | | para “capturar” el elemento actual de la iteración.

const primos = [_]u8{ 2, 3, 5, 7, 11 };

for (primos) |numero| {
    std.debug.print("Primo: {d}\n", .{numero});
}
Copied!

Importante: La variable capturada (numero) es una constante que contiene una copia del valor. No puedes hacer numero += 1 dentro del bucle para modificar el array original.

Modificar el array original (|*ptr|)

Si necesitamos modificar los elementos del array mientras iteramos, debemos capturar el puntero al elemento, usando un asterisco.

var numeros = [_]i32{ 1, 2, 3 };

for (&numeros) |*n| {
    n.* += 10; // Accedemos al valor a través del puntero y sumamos
}
// Ahora numeros es { 11, 12, 13 }
Copied!

Obtener el índice

A veces necesitamos saber en qué posición estamos. Zig permite capturar el índice añadiendo una segunda variable en la captura. Para que esto funcione, a menudo especificamos el rango 0...

const vocales = "aeiou";

// Iteramos sobre vocales y a la vez generamos índices desde 0
for (vocales, 0..) |letra, i| {
    std.debug.print("La vocal {d} es {c}\n", .{ i, letra });
}
Copied!

Iterar rangos numéricos

Para iterar “de 0 a 10”, Zig ofrece la sintaxis de rango inicio..fin.

// Itera del 0 al 9 (el límite superior es exclusivo)
for (0..10) |i| {
    std.debug.print("Iteración {d}\n", .{i});
}
Copied!

Fíjate que 0..10 excluye el 10. Si quieres iterar N veces, simplemente escribes 0..N.

Recorrer varias secuencias a la vez

Un for puede iterar sobre varias secuencias a la vez, siempre que tengan la misma longitud.

const nombres = [_][]const u8{ "Ana", "Beto", "Carla" };
const edades = [_]u8{ 25, 30, 22 };

// Avanza en ambos arrays simultáneamente
for (nombres, edades) |n, e| {
    std.debug.print("{s} tiene {d} años\n", .{ n, e });
}
Copied!

Esto elimina la necesidad de gestionar índices manuales (nombres[i], edades[i]) y hace el código mucho más seguro (el compilador o el runtime verificarán que las longitudes coincidan).

break y else en bucles

Tanto while como for soportan break para salir prematuramente. Pero Zig añade un giro interesante: los bucles pueden tener un bloque else.

El código dentro del else se ejecuta solo si el bucle terminó “naturalmente” (es decir, la condición se volvió falsa o se acabó el rango), pero NO si se salió usando break.

// Buscamos un número
for (numeros) |n| {
    if (n == objetivo) {
        std.debug.print("¡Encontrado!\n", .{});
        break;
    }
} else {
    // Solo se ejecuta si el for terminó sin encontrar nada
    std.debug.print("No se encontró el objetivo\n", .{});
}
Copied!

Esta estructura resulta útil en algoritmos de búsqueda y evita mantener una variable booleana encontrado fuera del bucle.