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 ~Tdefer
Belongs toa type — every value of itone piece of work in one scope
Writtenonce, in extend Twhere the work starts
Runswhen a value's life endswhen 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.
Do not defer a 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