Saltar al contenido

defer, defer_ok y defer_err

En esta página

defer programa una instrucción para ejecutarse cuando el ámbito actual finalice — ya sea por un retorno normal, un panic o cualquier otro camino. Múltiples defer en la misma función se ejecutan en orden LIFO (pila): el último registrado se ejecuta primero.

LIFO, limpieza práctica de recurso, defer en pánico y ámbito de bloque interno.

07-defer.zolo
Playground
// Feature: defer — schedule cleanup at the end of the block

// Syntax: `defer <stmt>`

// When to use: release a resource, close a file, log on exit.


// -- defer runs on EXIT of the function, in LIFO order --------

fn lifo_order() {
  defer print("a")  // last to register, first to run? NO

  defer print("b")
  defer print("c")
  print("body")
}

lifo_order()

// expected:

// body

// c

// b

// a

// (the most recently registered defer runs first — LIFO / stack)


// -- Practical case: ensure cleanup ---------------------------

fn process() {
  print("opening file")
  defer print("closing file")  // ensures closing


  print("reading data")
  print("processing")
}

process()

// expected:

// opening file

// reading data

// processing

// closing file


// -- defer on a normal return ---------------------------------

fn maybe_fail(fail: bool) {
  defer print("defer ran")
  if fail {
    panic("oops")
  }
  print("no panic")
}

try { maybe_fail(false) } catch e { print("caught") }

// expected:

// no panic

// defer ran


try { maybe_fail(true) } catch e { print("caught") }

// expected:

// defer ran

// caught

// (defer fires on panic too — per the spec exit table, panic counts

//  as an error exit and the function-level pcall recovers it before

//  the drain runs.)


// -- defer respects block scope -------------------------------

fn block_scope() {
  print("start")
  {
    defer print("inner")
    print("inside the block")
  }
  print("end")
}

block_scope()
// expected:

// start

// inside the block

// inner

// end

// (defer runs on EXIT of the block where it was declared)

defer es una instrucción, no una palabra clave a nivel de expresión. Puedes usar defer como nombre de variable fuera de un contexto de instrucción, pero dentro de la instrucción el compilador lo reconoce como defer.

Cuando el tipo de retorno es Result<T, E>, puedes ser más preciso: defer_ok se ejecuta solo cuando la función retorna Ok; defer_err se ejecuta solo cuando retorna Err. Las tres palabras clave (defer, defer_ok, defer_err) comparten el mismo stack LIFO:

Tríada defer/defer_ok/defer_err, enlaces que reciben el valor de retorno, patrón rollback/commit.

10-defer-ok-err.zolo
Playground
// Feature: `defer_ok` / `defer_err` — cleanup filtered by exit path.

// Syntax: `defer_ok [|v[: T]|] <expr>` / `defer_err [|e[: E]|] <expr>`

// When to use: differentiate cleanup that runs only on success

// (commit, metrics.success, log.info) from cleanup that runs only

// on failure (rollback, metrics.failure, alarms). Shares the same

// LIFO stack as plain `defer`. See `specs/defer-ok-err.md`.


use std::Result

// -- Triad ordering — single LIFO across the three keywords ----

fn op_ok() -> Result<int, str> {
  defer       print("A always")
  defer_err   print("B err — should NOT print")
  defer_ok    print("C ok")
  defer       print("D always")
  return Result.Ok(42)
}

print("--- op_ok ---")
let _ = op_ok()
// expected:

// --- op_ok ---

// D always

// C ok

// A always

// (B is filtered because the exit value is Ok)


fn op_err() -> Result<int, str> {
  defer       print("A always")
  defer_err   print("B err")
  defer_ok    print("C ok — should NOT print")
  defer       print("D always")
  return Result.Err("oops")
}

print("--- op_err ---")
let _ = op_err()
// expected:

// --- op_err ---

// D always

// B err

// A always


// -- Bindings — receive the return value / error --------------

fn compute() -> Result<int, str> {
  defer_ok  |v| print("ok with v")
  defer_err |e| print("err with e")
  return Result.Ok(100)
}

print("--- compute (ok) ---")
let _ = compute()
// expected:

// --- compute (ok) ---

// ok with v


// -- Typed binding ---------------------------------------------

fn typed_log() -> Result<int, str> {
  defer_err |e: str| print("typed err")
  return Result.Err("boom")
}

print("--- typed_log ---")
let _ = typed_log()
// expected:

// --- typed_log ---

// typed err


// -- Rollback pattern without a flag --------------------------

fn transfer(commit_ok: bool) -> Result<int, str> {
  print("begin tx")
  defer_err print("rollback tx")
  defer_ok  print("commit tx")

  if !commit_ok {
    return Result.Err("validation failed")
  }
  return Result.Ok(1)
}

print("--- transfer(true) ---")
let _ = transfer(true)
// expected:

// --- transfer(true) ---

// begin tx

// commit tx


print("--- transfer(false) ---")
let _ = transfer(false)
// expected:

// --- transfer(false) ---

// begin tx

// rollback tx

El patrón clásico de transacción resulta natural con defer_ok / defer_err: registra defer_err print("rollback") justo después de abrir la transacción, y defer_ok print("commit") a continuación. El camino feliz hace commit; cualquier return Err(...) antes del final ejecuta el rollback — sin flag booleano.

Desafío

En el ejemplo de defer, agrega un segundo bloque interno con dos defer y observa el orden de ejecución. ¿Qué ocurre con los defers externos?

Buscar en Zolo

9 resultados

enespt-br