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.
// 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)
deferes una instrucción, no una palabra clave a nivel de expresión. Puedes usardefercomo nombre de variable fuera de un contexto de instrucción, pero dentro de la instrucción el compilador lo reconoce comodefer.
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.
// 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?
Consulta también