defer expression schedules an expression for the exit of the current lexical
scope.
handle := open_resource()?
defer handle.close()
use_resource(handle)?The deferred expression is associated with the block containing the defer,
not merely with the whole function.
Registration happens only when execution reaches the statement. A defer inside a selected branch is not registered when another branch runs.
if tracing_enabled {
span := start_span()
defer span.finish()
execute()
}Here finish belongs to the conditional branch’s block and runs before that
branch leaves.
Exit paths
A defer runs when its scope exits normally and when control leaves through an explicit function return or propagated error.
fn process(name: string): int ! ResourceError {
handle := open_resource(name)?
defer close_resource(handle)
data := read_resource(handle)?
return data.len()
}The cleanup runs after the return value has been evaluated but before the function completes. This lets the return expression read the resource it is about to release.
Loop transfers unwind the lexical scopes they leave before continuing at their target. A defer in an inner loop block therefore belongs to each iteration’s block exit.
for name in names {
handle := open_resource(name)?
defer close_resource(handle)
process_resource(handle)?
}Each iteration releases its own handle before the next iteration begins. A defer placed outside the loop would instead wait for the surrounding scope.
Hard runtime teardown and host-process failure are outside ordinary source
control flow. Do not use defer as the only persistence guarantee for data
that must survive process loss.
Ordering
Defers registered in one scope run in reverse order.
defer first_cleanup()
defer second_cleanup()
// second_cleanup, then first_cleanupWhen several scopes are exited, the innermost scope is cleaned first.
defer outer_cleanup()
{
defer inner_first()
defer inner_second()
}
// inner_second, inner_first, then later outer_cleanupThis last-in, first-out order mirrors nested acquisition.
Evaluation
The deferred expression executes at scope exit. Names in it refer to the captured lexical places kept alive for that cleanup.
mut count := 0
defer println(count)
count = 3
// prints 3 at scope exitIf a specific value must be preserved independently of later mutation, bind a separate immutable snapshot and refer to it.
starting_count := count
defer report_start(starting_count)Keep deferred expressions small. A named cleanup function is often clearer than a large inline block.
The values needed by a deferred expression remain live until it runs. Capturing a large buffer or resource can therefore intentionally extend its lifetime to the end of the scope; introduce a smaller snapshot when only a small piece of state is required.
Errors
Postfix ? is rejected anywhere inside a deferred expression. There is no
caller continuation to which a cleanup failure can transparently propagate
after the surrounding exit is already underway.
defer maybe_close()? // errorHandle a fallible cleanup explicitly.
defer match maybe_close() {
Ok(_) => {}
Err(error) => report_cleanup(error)
}Choose a policy appropriate to the resource: log, record, retry safely, or ignore a documented idempotent close failure. Do not silently discard an important transactional error.
Suspension
A deferred call participates in ordinary effect inference. If it can suspend, the enclosing function and its callers must permit that effect, and the compiler preserves the deferred state across the suspension.
Prefer synchronous, idempotent cleanup where possible. Long-running work in a defer delays every exit path and makes cancellation behavior harder to reason about.
Destruction
Compiler-managed value destruction, reference counting, weak-reference
maintenance, and user Drop dispatch do not require a source defer.
Use defer when the paired action is a semantic operation visible to the
program:
- close a host handle;
- finish a tracing span;
- restore temporary process or task state;
- release a manually acquired external lease;
- run a best-effort compensating action.
Use a transaction block for database commit/rollback semantics rather than
reimplementing them with deferred SQL.