El concepto detrás de defer en Zig es muy sencillo: una instrucción precedida por esta palabra siempre será ejecutada al final del bloque de contexto actual.

¿Por qué alguien querría hacer eso? Lo veremos a continuación.

Caso de uso

Limpieza de un asignador de memoria

Recordemos este pasaje en nuestra entrada sobre un Arraylist en Zig:

El ArrayList acepta un Tipo (T), i32 en nuestro ejemplo, como argumento y la gestión de memoria ocurre de forma automática durante el ciclo de vida del mismo. Sin embargo, la inicialización y destrucción del vector debe hacerse explicitamente:

    const allocator = std.heap.page_allocator;
    var list = ArrayList(i32).init(allocator);
    defer list.deinit();

La variable allocator guarda el asignador de memoria page_allocator que provee la biblioteca estándar de Zig. La variable list inicializa el Arraylist. Por último, hacemos la destrucción de list usando el método deinit() acompañado de la palabra defer.

En este escenario, posterior a la instrucción de:

    defer list.deinit();

Tenemos algunas otras instrucciones que el programa va a ejecutar:

    try list.append(100)
    std.debug.print(
        "El Elemento en el indice 0 es: {}",
        .{list.items[0]},
    );

Todo lo anterior se encuentra en el ámbito de la funcion:

pub fn main() {
}

Siguiendo esa lógica, muchas personas pensarían que la instrucción de:

    std.debug.print(
        "El Elemento en el indice 0 es: {}",
        .{list.items[0]},
    );

Sería lo último en ejecutarse. Equivocados. En realidad lo último que se ejecuta en el contexto de la función main() es la instrucción de:

    defer list.deinit();

Y tiene sentido si revisamos la traducción literal al Español del término defer: significa “posponer” o “aplazar”. Lo que estamos haciendo es basicamente aplazando una instrucción para el final del bloque {} de contexto actual (main()).

Entonces, ¿Por qué no simplemente dar la instruccion al final del bloque de contexto? Bueno, la razón es porque como seres humanos tendemos a olvidar fácilmente algunas cosas o pasar detalles por alto. Por lo cual, declarar defer justo después de hacer una asignación manual de memoria es mucho más sencillo que recordar hacerlo al final de cada bloque de contexto.

Es como decirle al compilador: “hey, no se te olvide hacer limpieza y eliminar este asignador de memoria”.

A medida que estudiamos Zig, veremos si es posible que el compilador retorne error si olvidamos dar la instrucción de defer o no. Lo descubriremos en una posible futura entrega.

Otro caso de uso relativamente común para defer es en la manipulación de archivos. En ese caso, se hace para no olvidar cerrar un archivo que se ha abierto usando Zig.

Espero que haya quedado un poco más claro el funcionamiento de defer en Zig.