Pular para o conteúdo

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.

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 é uma instrução, não uma palavra reservada no nível de expressão. Você pode usar defer como nome de variável fora de um contexto de instrução, mas dentro da instrução o compilador o reconhece como defer.

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.

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

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?

Buscar no Zolo

9 resultados

enespt-br