Skip to content

Choose when cleanup runs

NativeBrowserNode2 min read

Use deferred cleanup when destroying a value during a sensitive part of a program would cause an unwanted pause.

At a glance

defer {
    paint(new_tree);
} // the old tree is freed here
  • Values are freed as soon as their last owner goes away.
  • defer moves expensive cleanup out of a time-sensitive section.

Back to overview

defer: wait until after the important work to run destructors

Normally, when nothing points at an object anymore, Dream runs its del (if any) and frees it right then. That is what you want almost everywhere.

Sometimes that “right then” is a bad moment: you drop last year’s UI tree or a particle buffer, and the destructor storm runs before you finish drawing or simulating this frame. defer { … } keeps the objects logically gone (nothing can use them), but runs the actual cleanup at } — after the work you care about.

defer {
    old_root = new_tree(); // old tree is no longer needed
    paint(new_tree); // do this before a huge cleanup hitch
} // `del` / free of the old tree runs here

Use it when there is a deadline in the middle of a tick (paint, simulate, submit a frame) and a large graph dies in the same tick.

Skip it when you are just allocating and dropping in a loop with nothing urgent in between. Cleanup is not cheaper with defer — it is only later (and can use a bit more memory until }). Needless defer is extra bookkeeping.

Braces are required. await is not allowed inside defer.

import system;

class Tracked {
    public id: int;

    public constructor(id: int) {
        this.id = id;
    }

    del() {
        System.println(this.id);
    }
}

fun main() {
    defer {
        let x = Tracked(1);
        System.println(x.id); // last use of `x` — `del` waits
        System.println(999);
    } // now prints 1
}

Optional: how much to clean at }

Form Cleanup at the closing }
defer { … } A batch of 256 objects; the outermost defer then finishes the rest
defer(q) { … } q objects (q is a uint); the outermost defer then finishes the rest
defer(0) { … } None; the work waits for a later defer, with at most 16,384 objects left pending
  • defer { … } — clean a batch at } (256 objects), then finish the rest if this is the outermost defer, so work does not sit until the program exits.
  • defer(q) { … } — q is a uint (plain 256 is fine). That many objects are cleaned at this }. defer(0) means “don’t clean on this }” — useful around a game loop so inner defer(256) slices can spread cleanup across frames.
  • Nested defer share one cleanup list. While you are still inside some defer, leftover work can wait for the next one, but the list is capped (16 384 objects) so memory cannot grow without bound.
  • When you compile for the browser or Node, memory is freed as soon as the last use ends. dream run may wait until the end of the block.
  • On native, releasing the last reference to a string can also defer its cleanup until }. Temporary strings may be reclaimed earlier.

Use a small cleanup budget around frame-by-frame work only after measuring pauses in your application. Keep the deferred region short so unused objects do not accumulate.