Note: Only the rc and so regions are currently supported.

To allocate a new value, we ask a region to get it done:

imm nbrref = +rc-imm 5  // the 'rc' region counts references

There are many kinds of regions, each using a different strategy for allocating memory and then later safely reclaiming that memory after an object has no references that point to it. Variations in their strategies can deliver noticeably different behavior with regard to:

Cone offers a wide selection of library-implemented regions, because no one strategy handles all these criteria well. This power of choice ensures each allocated object can select the optimal region that best serves the way the program needs that object to behave.

This page does not list every possible region that can be used by a Cone program, as new ones can be defined within the language itself. Instead, this page summarizes the five popular region strategies used to allocate new objects pointed at by an owning reference. The focus is to describe the mechanism used by each of these region strategies and assess how they compare on the above criteria. In reality, each of these region strategies may be implemented by multiple concrete regions that vary somewhat in how they work (e.g., +rc might only work within a single thread, whereas +arc might allow multiple threads to share references to the same object).

Single Owner

The single-owner region strategy's mechanism, strengths and weaknesses derive from a very simple constraint: there is only ever one owning reference to an object it allocates. That reference may be passed around from function to function or from thread to thread, but it does so using move mechanics to ensure no copy of the reference is ever made. Because there is only one reference to the object, the memory for that value can be automatically finalized and freed when the reference expires.

Here is the simple definition of a single-owner region:

region @move so:      // @move ensures move semantics on references, i.e. no aliasing
    fn alloc(size usize) Option[*u8]:
	    malloc(size)  // May use any general-purpose memory allocator
	fn free(self &uni rc):
	    free()

Single owner is a good choice if you are looking for:

The key shortcomings are:

Reference Counting

As the name suggests, the rc region keeps a running counter of the number of references to each allocated object. When the object is first allocated, its counter starts at 1. Every time a copy is made of an rc owning reference, the counter is incremented. Whenever an owning reference goes out of scope, the counter is decremented. When the counter reaches 0, the object is reclaimed.

Here is the simple definition of a reference counting region, where aliasing happens automatically:

region rc:
    cnt u32
    fn alloc(size usize) Option[*u8]:
	    malloc(size)
	fn init(self &uni rc):
	    cnt = 1
	fn alias(self &uni rc):
	    ++cnt
	fn dealias(self &uni rc):
	    if (--cnt == 0)
		  free()
	fn free(self &uni rc):
	    free()

Variations on this algorithm may be built to support multi-threaded use (with atomics) and leverage move semantics and explicit aliasing (to improve throughput).

Reference counting is a good choice if you are looking for:

The biggest shortcomings are:

To prevent cycle-based memory leaks, one can use a variation that supports weak references to make any cycle breakable. A weak reference may be obtained from any owning reference:

childnode.parent = parentnoderef.weak

Tracing Garbage Collection

A tracing GC region captures metadata about every object it allocates, including detailed information about its references to other allocated objects. Periodically, the region's runtime garbage collector will trace out all references reachable from a root set of objects (including execution stacks). Any allocated value that cannot be traced from the root is considered unreachable, and therefore is safe to automatically finalize and free.

Tracing GC is a good choice if you are looking for:

The biggest shortcomings are:

Arena

An arena region begins life pre-allocating a large (and growable) slab of memory. This is the arena. Every new allocation takes a bite out of this arena using a fast bump pointer. Allocations are never individually freed. The entire arena is freed as a single event when the program ends.

Arena region is a good choice if you are looking for:

The biggest shortcomings are:

An arena region is a surprisingly good fit for a limited-lifetime program that allocates a lot of small objects but wants optimal performance. This applies well to many command-line tools, including compilers.

Pool

The mechanism for pools derives from this constraint: all objects in a pool have the same type and size. A pool region begins with a pre-allocated (and growable) memory area that is logically partitioned into identically-sized slots. Allocating space for a new object is efficient: it either reuses a slot found on a linked-list of free slots, or else uses a bump pointer to carve out a new slot from the unused area.

A pool region must be created before using it to allocate values:

mut eventPool RcPool<Event>

This example creates a new region called eventPool. All its allocated object have the type Event (which might be an enum type). This pool is an RcPool, which means references to pool-allocated objects are reference-counted, thereby determining when to free the object. An SoPool would use single-owner references, and so on.

Once a pool region is defined, the region may be specified to allocate a new value:

imm newevent = &eventPool mut Event::QuitEvent[]

Pools are a good choice if you are looking for:

The biggest shortcoming is:

When a program has a large number of objects of the same type being regularly allocated and freed, as is true in many games, pools can be very useful strategy. This is particularly true when functions process all objects sequentially in a cache-friendly way.

_