Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Finalization

A type that claims the fin capability runs custom cleanup when its owner is released. The cleanup is a compiler-owned receiver contract: a pro named finalize that takes the value by value and returns non.

typ File()(fin): rec = { descriptor: int };

pro (File)finalize(): non = {
    close(self.descriptor);
    return;
};

The finalizer is a pro because releasing a foreign resource is an effect. A fin type cannot also claim copy: a value with cleanup is not trivially duplicable.

Scope-exit cleanup

The finalizer runs automatically when the owning scope exits, exactly once per value:

fun[] main(): int = {
    var handle: File = { descriptor = 3 };
    return handle.descriptor;
    // handle.finalize() runs here, as the scope exits
};

When a value is moved, responsibility for finalization moves with it: the new owner finalizes it, and the original binding does not.

A [mov] capture into a dfr or edf block is the one move that stays in the frame. The delayed block replays at scope exit, so the value is finalized right after the block body runs:

fun[] main(): int = {
    var handle: File = { descriptor = 3 };
    dfr[handle[mov]] {
        observe(handle.descriptor);
    };
    return 0;
    // the deferred body runs, then handle.finalize()
};

What can own a fin value

A finalizer runs only for a value that a binding, a parameter or a dfr/edf capture owns directly. A fin value nested inside another value — a record field, a vec/arr/seq element, a set/map member, an opt or err payload — has no such owner, so nothing would ever call its finalizer. The compiler rejects those positions instead of skipping the cleanup silently:

var handles: vec[File] = { { descriptor = 3 } };
// rejected: a nested 'fin' value would never be finalized

Give each fin value its own binding and transfer it with [mov].

Early finalization

[fin]value runs the finalizer immediately and invalidates the source. The value is finalized exactly once, so scope-exit cleanup does not run a second time:

var handle: File = { descriptor = 3 };
observe(handle.descriptor);
[fin]handle;
// handle is now consumed; no second finalize at scope exit