While

Para construir un bucle usando while usamos la siguiente estructura:


while (condición booleana){

     // Código ejecutado de manera cíclica mientras se cumpla la condición

} 


El bloque de código entre llaves se ejecutará una y otra vez, siempre y cuando la condición booleana sea verdadera:

El bucle while es muy usado, cuando el número de iteraciones es desconocido, sin embargo si se conoce el número de iteraciones, se suele usar una variable contador que se va incrementando (o decrementando) en cada iteración, para controlar la condición de salida:

  // contador para el índice

  var n_i: usize = 0;

  // iteramos mientras n_i es menor o igual a 10

  while (n_i <= 10) {

    // ejecutamos instrucciones y vamos incrementando n_i

    n_i += 1;

No es la única manera de controlar un bucle while, pero es muy común hacerlo así. En Zig, para no tener que escribir este incremento dentro del bloque, podemos ponerlo así de forma más compacta:

  var n_i: usize = 0;

  // iteramos mientras n_i es menor o igual a 10

  while (n_i <= 10):(n_i += 1) {

    // ejecutamos instrucciones pero ya no incrementamos n_i dentro del bloque

En Zig, es posible, pero no obligatorio, ampliar un while con un bloque else:


while (condición booleana) : (incremento opcional) {

     // Código ejecutado de manera cíclica mientras se cumpla la condición

} else {

     // Código ejecutado sólo una vez cuando no se cumple la condición

}


El bloque else de un while se ejecuta siempre y una sola vez, ya sea:

Eso sí - podemos interrumpir el bucle while  en cualquier momento usando la palabra clave break. Esto hará que el bucle se detenga de inmediato, sin siquiera pasar por el bloque else (si existe).

Y si lo que queremos es saltar directamente al inicio del bucle, ignorando el resto del bloque podemos usar la palabra clave continue.


while (condición booleana){

     // para saltar inmediatamente al inicio del  bucle usamos continue

     // para salir inmediatamente del bucle usamos break

} else {

     // ejecutado sólo una vez cuando no se cumple la condición y si no usamos break

}


En el siguiente ejemplo el jugador está buscando en las taquillas del laboratorio todas las pociones curativas que pueden ayudarle a reparar el daño infligido por el ataque de sus enemigos.

Sabe que:

Solo vamos a listar aquellas pociones que de verdad tienen efectos curativos:

while.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    // diferentes pociones mágicas

    const a_lockers =

        [_] // no especificamos el número exacto

        []const u8{ // nombres como textos de longitud fija

            "járabe de la abuela", // 0

            "salsa barbacoa", // 1

            "limonada", // 2

            "tabasco", // 3

            "mayonesa", // 4

            "ketchup", // 5

            // ...

        };

    print("Las pociones curativas en las taquillas:\n", .{});

    // contador para el índice

    var n_i: usize = 0;

    // repasando el inventario en busca de las pociones curativas:

    while (n_i < a_lockers.len) : (n_i += 1) {

        // recuperamos la poción en el índice

        const s_potion = a_lockers[n_i];

        // si es salsa barbacoa no se cuenta

        if (n_i == 1) continue;

        // si es tabasco salimos del inventario

        // después del tabasco no hay nada que no sea comida

        if (n_i == 3) break;

        print("\tTaquilla {}: {s}\n", .{ n_i + 1, s_potion });

    }

}

$ zig run while.zig

Las pociones curativas en las taquillas:

        Taquilla 1: jarabe de la abuela

        Taquilla 3: limonada

En el ejemplo:

While infinito

Antes, cuando hablamos de funciones recursivas, vimos que si una función se llama a sí misma demasiadas veces sin detenerse, provoca que se rompa el programa:  con cada llamada se come un poco de la memoria reservada y al final se agota. Como vimos, este gasto de memoria sucede aunque no se le pase ningún parámetro en las llamadas.

        Y ¿Qué sucede con un while que no acaba nunca?

while_infinite.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    print("antes del while\n", .{});

    while (true) {

        // nada aquí de momento

    }

    print("Nunca llega aquí\n", .{});

}

~$ zig run while_infinite.zig

antes del while

Nada. Literalmente no pasa nada. Un while infinito, por sí solo no consume más recursos en cada iteración - aquí gira en el vacío a la espera de ser interrumpido de alguna manera. Para detenerlo podemos pulsar las teclas Ctrl y c a la vez (Ctrl+c) - así se envía una señal de interrupción al programa desde la terminal.

Obviamente, si dentro del bucle tenemos código que consume recursos sin liberarlos - como asignar memoria, abrir archivos sin cerrarlos etc. puedes colapsar el programa. Pero por sí mismo un while no lo hará..

While con captura de valores opcionales

En Zig existe un concepto que mencionamos justo ahora porque combina muy bien con los bucles while: la captura de valores opcionales.

Un valor opcional en Zig puede tener un valor concreto o ser null - es decir nada, no tener nada. Por lo tanto su tipo tampoco se corresponde con el tipo de valores que esperamos manejar. Es como un contenedor vacío. ¿Por qué y cómo necesitamos gestionar esto?

El jugador llega a una habitación y encuentra unas cajas misteriosas que contienen energía pura. Algunas de estas cajas, por desgracia, contienen el vacío absoluto - su valor es literalmente null. Cuando el jugador abre una caja puede encontrarse un número positivo - la energía almacenada o… nada - pero ese nada no es un 0. No es un numéro ni ningún valor… y ¿qué sucede?

while_unwrap_null.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_boxes = [_]u8{

        100,

        10,

        null, // caja vacía

        30,

        120,

        null, // caja vacía

    };

    var n_i: usize = 0;

    while (a_boxes[n_i]) |n_pts| : (n_i += 1) {

        print("{}. ¡Has ganado {} puntos de energía!\n", 

        .{ n_i + 1, n_pts });

    } else {

        print("{}. ¡Caja vacía! \n", .{n_i + 1});

    }

}

$ zig run while_unwrap_null.zig

while_unwrap_null.zig:8:9: error: expected type 'u8', found '@TypeOf(null)'

        null, // caja vacía

        ^~~~

El programa ha “explotado”. Zig espera que los elementos del array a_boxes fueran del tipo u8 pero de repente el jugador “abre” la caja y el programa se encuentra un null. Eso no es un número. No es nada y por eso falla en tiempo de compilación.

Para poder gestionar un dato que podría ser null tenemos que definir estos elementos indicando su tipo con un signo ? delante de su tipo habitual. En este caso será ?u8:

while_unwrap_null_fix.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_boxes = [_]?u8{ // <- el tipo opcional indicado

        100,

        10,

        null, // caja vacía

        30,

        120,

        null, // caja vacía

    };

    var n_i: usize = 0;

    while (a_boxes[n_i]) |n_pts| : (n_i += 1) {

        print("{}. ¡Has ganado {} puntos de energía!\n", 

        .{ n_i + 1, n_pts });

    } else {

        print("{}. ¡Caja vacía! \n", .{n_i + 1});

    }

}

$ zig run while_unwrap_null_fix.zig

1. ¡Has ganado 100 puntos de energía!

2. ¡Has ganado 10 puntos de energía!

3. ¡Caja vacía!

El programa ya no se rompe como antes. Sin embargo, como el while con captura  se detiene cuando el valor resulta ser un null todo se para en la tercera caja. No está tan mal pero se puede mejorar. El jugador quiere poder abrir todas las cajas. ¿Qué podemos hacer para recorrer y procesar todos los elementos?

Esta es una buena ocasión para aprovechar lo que ya sabemos hacer con switch: el salto a una etiqueta, y combinarlo con lo que hemos aprendido sobre los while. Sí, esto es  complicarlo un poco, luego vemos una manera más sencilla de hacerlo, pero usar continue :etiqueta valor es bastante divertido ¿no crees?

while_unwrap.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_boxes = [_]?u8{ // tipo opcional

        100,

        10,

        null, // caja vacía

        30,

        120,

        null,// caja vacía

    };

    var n_i: usize = 0;

    loop: switch (n_i) {

        else => {

            while (a_boxes[n_i]) |n_pts| : (n_i += 1) {

                print("{}. ¡Has ganado {} puntos de energía!\n", 

                .{ n_i + 1, n_pts });

            } else {

                print("{}. ¡Caja vacía! \n", .{n_i + 1});

                n_i += 1;

                if (n_i < a_boxes.len) continue :loop n_i;

            }

        },

    }

}

$ zig run while_unwrap.zig

1. ¡Has ganado 100 puntos de energía!

2. ¡Has ganado 10 puntos de energía!

3. ¡Caja vacía!

4. ¡Has ganado 30 puntos de energía!

5. ¡Has ganado 120 puntos de energía!

6. ¡Caja vacía!

Ahora el jugador abre todas las cajas y el programa es capaz de gestionar tanto los valores con el tipo esperado como los null.

        Saltar a etiquetas, me parece muy divertido, lo admito. Pero la mayoría de las veces hay una manera más simple. Si alguna vez sientes la tentación (como me pasa a mí), piensa que posiblemente hay una solución sin saltos… aunque sea más aburrida:

while_unwrap_2.zig

const std = @import("std");

const print = std.debug.print;

pub fn main() void {

    const a_boxes = [_]?u8{

        100,

        10,

        null,

        30,

        120,

        null,

    };

    var n_i: usize = 0;

    while (n_i < a_boxes.len) : (n_i += 1) {

        if (a_boxes[n_i]) |n_pts|

            print("{}. ¡Has ganado {} puntos de energía!\n", 

            .{ n_i + 1, n_pts })

        else

            print("{}. ¡Caja vacía! \n", .{n_i + 1});

    }

}

$ zig run while_unwrap_2.zig

1. ¡Has ganado 100 puntos de energía!

2. ¡Has ganado 10 puntos de energía!

3. ¡Caja vacía!

4. ¡Has ganado 30 puntos de energía!

5. ¡Has ganado 120 puntos de energía!

6. ¡Caja vacía!


Funciones recursivas
For
© 2025 Zen of Zig