136 / Types and patterns

defer runs when its block closes

A defer runs when control LEAVES THE BLOCK where it is written: by falling off the end, by return, by ? on an Err, by break or by continue. Up to 0.35.0 only the one at the top level of a function ran, and a block's defer was dropped silently; from 0.36 on it runs.

136-defer-al-cerrar-bloque.nxSource →
// Nyx by Example: `defer` corre al cerrar el bloque que lo contiene.
//
// Un `defer` se ejecuta cuando el control SALE DEL BLOQUE donde está escrito: al caer al final,
// por `return`, por `?` con Err, por `break` o `continue`. (Hasta 0.35.0 solo corría el del nivel
// superior de la función; el de un bloque se descartaba en silencio. Ver
// docs/gotchas/defer-runs-at-block-exit.md.)
//
// Lo que esta receta muestra:
//   1. `defer` dentro de un loop: libera algo en CADA iteración;
//   2. `defer` dentro de un `if` con `return` temprano;
//   3. el orden: LIFO dentro del bloque, de adentro hacia afuera entre bloques;
//   4. el cuerpo del defer ve los valores AL SALIR, no al declararlo.
//
// Límite: un `throw` que sale de la función no corre sus `defer`
// (ver docs/gotchas/defer-not-on-throw.md). Usa Result y `?` si necesitas la limpieza.

var g_log: String = ""

fn reset() {
    g_log = ""
}

// Un «recurso» por iteración: se abre al entrar y se cierra al terminar el cuerpo.
fn por_iteracion() -> int {
    var i = 0
    while i < 3 {
        g_log = g_log + "abre" + int_to_string(i) + ";"
        defer { g_log = g_log + "cierra" + int_to_string(i) + ";" }
        i = i + 1
    }
    return i
}

// El defer del bloque corre también cuando el return sale desde adentro.
fn temprano(b: bool) -> int {
    if b {
        defer { g_log = g_log + "liberado;" }
        g_log = g_log + "usando;"
        return 1
    }
    g_log = g_log + "sin-recurso;"
    return 0
}

// LIFO dentro del bloque; interno, externo y función entre bloques.
fn orden() -> int {
    defer { g_log = g_log + "fn;" }
    if true {
        defer { g_log = g_log + "externo-1;" }
        defer { g_log = g_log + "externo-2;" }
        if true {
            defer { g_log = g_log + "interno;" }
            return 7
        }
    }
    return 0
}

fn main() {
    reset()
    assert(por_iteracion() == 3)
    // el defer ve `i` al salir: ya incrementado
    assert(g_log == "abre0;cierra1;abre1;cierra2;abre2;cierra3;")

    reset()
    assert(temprano(true) == 1)
    assert(g_log == "usando;liberado;")
    reset()
    assert(temprano(false) == 0)
    assert(g_log == "sin-recurso;")

    reset()
    assert(orden() == 7)
    assert(g_log == "interno;externo-2;externo-1;fn;")

    print("ok")
}
Outputstdout
ok

How it works

Four things are shown. A defer inside a loop releases something on EVERY iteration. A defer inside an if with an early return still runs. The order is LIFO within a block and from the inside out between nested blocks. And the body of a defer sees the values AT EXIT, not at the moment it was declared. The program prints ok when every one of those holds.

The limit: a throw that leaves the function does not run its defers. If you need the cleanup, use Result and ?.