defer, defer_ok e defer_err
Nesta página
defer agenda uma instrução para rodar quando o escopo atual encerrar —
seja por retorno normal, por panic ou por qualquer outro caminho. Múltiplos
defer na mesma função executam em ordem LIFO (pilha): o último registrado
roda primeiro.
LIFO, limpeza prática de recurso, defer em pânico e escopo de bloco 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)
deferé uma instrução, não uma palavra reservada no nível de expressão. Você pode usardefercomo nome de variável fora de um contexto de instrução, mas dentro da instrução o compilador o reconhece comodefer.
Quando o tipo de retorno for Result<T, E>, você pode ser mais preciso:
defer_ok roda apenas quando a função retorna Ok; defer_err roda apenas
quando retorna Err. Os três keywords (defer, defer_ok, defer_err)
compartilham o mesmo stack LIFO:
Tríade defer/defer_ok/defer_err, bindings que recebem o valor de retorno, padrão 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
O padrão clássico de transação fica natural com defer_ok / defer_err:
registre defer_err print("rollback") logo após abrir a transação, e
defer_ok print("commit") logo em seguida. O caminho feliz commita; qualquer
return Err(...) antes do final faz o rollback — sem flag booleano.
Desafio
No exemplo de defer, adicione um segundo bloco interno com dois defer e
observe a ordem de execução. O que acontece com os defers externos?
Veja também