Defer
A defer statement registers a statement now and runs it when the enclosing scope ends, by whichever way it ends. Cleanup written once, on the line after the work it undoes, then covers every exit — including ones added later.
Syntax
defer statement
The deferred part is exactly one statement: an expression statement such as a call, an assignment, or a statement with its own body such as if. A bare block is not a statement, so defer { … } does not parse:
error: expected an expression before '{'
To defer several steps, write several defers — remembering that they run in reverse — put the steps in a function and defer the call, or defer an if whose body holds them.
struct Valve {
name: char8[..];
}
extend Valve {
func Open(self: &Valve) {
PrintLine("open {}", self.name);
}
func Close(self: &Valve) {
PrintLine("close {}", self.name);
}
}
func Fill(litres: int32) -> bool {
let supply = Valve { name: "supply" };
supply.Open();
defer supply.Close();
let drain = Valve { name: "drain" };
drain.Open();
defer drain.Close();
if litres > 100 {
return false;
}
PrintLine("filling {} litres", litres);
return true;
}
Both exits close both valves, the drain first.
Semantics
Registration happens at run time. A defer counts only once execution reaches it. A return before the defer line leaves without running it, and a defer inside a branch that is not taken never registers.
It belongs to its scope. A deferred statement runs when the block it is written in ends: the function body, the body of an if, or one pass of a loop body — so a defer in a loop runs once per pass, not once after the loop. The scope may end by reaching its closing brace, by return, or by fail and ? propagation.
Last registered, first run. Several defers in one scope run in reverse registration order, and the defers of nested scopes run innermost first.
It is evaluated when it runs. A deferred statement reads its variables at the end of the scope, not at the defer line, so it sees every change made in between.
Each function has its own. A function's defers run when that function's scopes end; calling a function never runs, or inherits, the caller's defers. Each instantiation of a generic function has its own as well.
Return and defer
return expr; evaluates expr exactly once and keeps the result — performing the copy or move into the return value — before any deferred statement runs. Whatever the defers change afterwards, the caller receives the kept value:
func Countdown() -> int32 {
var remaining: int32 = 3;
defer PrintLine("deferred code sees {}", remaining);
defer remaining = 0;
return remaining;
}
Countdown() returns 3, and the deferred PrintLine prints deferred code sees 0. The same holds for every part of an aggregate return value. A return with no value runs the same defers without capturing anything.
That ordering makes "hand out the current value, then advance" a two-line method:
struct Dispenser {
next: int32;
}
extend Dispenser {
func Take(self: &var Dispenser) -> int32 {
defer self.next += 1;
return self.next;
}
}
Defers and destructors
When a scope ends, its deferred statements run first, newest first, and then its values are destroyed, newest first. Registration and declaration order are not interleaved:
func Party() {
let ada = Guest { name: "Ada" };
defer PrintLine("lights off");
let bob = Guest { name: "Bob" };
defer PrintLine("music off");
}
flowchart LR
r["return value<br/>captured"] --> d["defers<br/>newest first"]
d --> x["destructors<br/>newest first"]
x --> c["control reaches<br/>the caller"]Party prints music off, lights off, Bob leaves, Ada leaves. On a return, the full order is: the return value is captured, the defers run, the locals are destroyed.
Destructor ~T | defer | |
|---|---|---|
| Belongs to | a type — every value of it | one piece of work in one scope |
| Written | once, in extend T | where the work starts |
| Runs | when a value's life ends | when the enclosing scope ends |
A destructor cleans up after a value. defer is for cleanup that belongs to a piece of work rather than to any one value's life.
Panics
A panic does not unwind, so no deferred statement runs — not in the panicking function and not in its callers.
break and continue skip a loop body's defers.A pass of a loop body that ends with
break or continue should run the defers that pass registered. rux 0.4.0 does not yet run them on those exits — it runs only the destructors — so a deferred statement in a loop body runs only on passes that reach the end of the body, or leave by return or fail.return.A deferred statement runs while the function is already returning, so it cannot return in turn. rux 0.4.0 does not yet reject
defer return …;, and a program containing it crashes when built.See also
- Destructors — cleanup that belongs to a value
- Return — leaving a function with a value
- Propagation —
?andfail, which run defers on the way out - Learn: Defer, Defer return