Resources and Memory

Some things a program takes have to be given back: temporary files, transactions, locks, network connections, and heap memory. Refingere's tool for all of them is defer, which runs cleanup however a block ends. Heap memory comes from a context value, so a test can measure it, limit it, and check nothing leaked. This page covers both.

Defer

defer schedules an expression to run when the current block ends - at its end, at a return or break, or when an error leaves it through error or !!. Written right after the thing it cleans up, it keeps the two together, and every exit path is covered without repeating the cleanup. See Defer.

Choosing when a defer runs

An attribute on a defer narrows down which endings it runs for:

  • @onError - only when the block ends with an error. For undoing work that didn't finish.
  • @onReturn - only on a return. For confirming work that did.
  • @onYield - each time a generator pauses at a yield.
  • @onBlockEnd - whenever the block ends, which is what a plain defer does.

See the defer attributes.

A defer belongs to the block it's written in, so one inside a loop body runs at the end of each iteration, not once after the loop. See Word Count.

In a generator, a plain defer runs once, when the sequence is finished. @onYield runs at every pause, for work that shouldn't wait - flushing, releasing a lock while the caller has control.

Heap memory

Memory beyond ordinary values comes from &heapAllocator, a context value like any other. allocate returns a block of Bytes, or an OutOfMemoryError error, so running out of memory is an ordinary error to handle, not a crash. A caller can inject a different allocator - an arena, a pool, a tracking one - for everything it calls.

Pointers

Three pointer types cover the usual kinds of ownership:

  • Pointer<T> is a raw address. It doesn't own what it points to, so memory from Pointer.allocate stays until free() is called.
  • RcPointer<T> is shared ownership: each clone is one more owner, and the value is freed when the last one is gone.
  • WeakPointer<T> refers to an RcPointer's value without owning it, so a cycle - a child pointing back at its parent - doesn't keep both alive forever. upgrade() gives an owner back while the value still exists.

Pointer.allocate and RcPointer.new take their memory from &heapAllocator. See the Types reference for their methods.

Types that own memory

A type holding memory it allocated gives it a free method, and the code that makes one defers the free on the next line. The pairing is visible where it matters, and covers errors as well as normal returns. See Checking for leaks for a complete one.

Checking for leaks

TrackingAllocator passes every request on to another allocator and counts as it goes: live() is what's still allocated, peak() the most there ever was, and allocations() how many requests succeeded. Given a limit, it refuses requests beyond it with OutOfMemoryError, so a test can walk through the out-of-memory path and check that it cleans up too.