Memory management
Unnameable manages heap memory automatically through a system called baton. When you declare a variable with heap you are opting into this system — the compiler tracks the variable's lifetime and inserts frees for you.
Stability notice: The baton system is under active development. Known issues include double frees and memory leaks in certain patterns involving assignments and
persist. The examples on this page represent cases that currently work correctly.
The heap modifier
Declaring a variable with heap tells the compiler three things — allocate this on the heap, use the built-in allocator, and manage the memory for me:
The compiler calculates the size of the type, requests memory from the allocator, and will free it automatically.
Last-use freeing
Baton frees memory at the last use of a variable, not at the end of its scope. This is more aggressive than scope-based cleanup — memory is released as early as possible.
Here x is never used after its declaration. The compiler frees it immediately at the declaration point rather than waiting for the function to return.
In this example p is freed as soon as it is no longer needed. x and y are freed at the assignment y = x + 1 which is their last use.
Pointers and lifetime extension
When a pointer is bound to a heap variable it takes responsibility for that variable's memory. The variable will not be freed while the pointer is alive:
p holds responsibility for x. When p is freed it also frees x. You do not need to manage x separately.
persist
If you want a heap variable to live until the end of its scope rather than being freed at last use, mark it with persist:
x is freed at last use. y is held until the scope ends.
Note:
persisthas known issues with memory leaking in some patterns. Use with caution until this is resolved.
Branches and loops
The compiler classifies heap variables as either native or foreigner relative to each block.
A native is a heap variable declared inside the block. It is treated normally — freed at last use within that block:
z is native to the if block. It is freed inside the block at its last use.
A foreigner is a heap variable that was declared outside the block but used inside it. The compiler guarantees a foreigner is never freed inside the block — it waits until after the block exits:
x is a foreigner to the if block. No matter which branch executes, x is guaranteed to be freed after the branch merges — never inside it.
The same native/foreigner rules apply to while and for loops.
Bunkering
When the compiler cannot safely determine where to free a variable it falls back to bunkering — the variable is held until the end of its scope and freed there. This is the safe fallback and guarantees no dangling pointers, at the cost of holding memory slightly longer than necessary.
You will not see bunkering directly — it happens automatically when the compiler needs it.
Custom allocators
The default heap modifier uses the compiler's built-in allocator. Custom allocators can be plugged in via an interface:
Custom allocators are covered in depth in the advanced memory section.