Allocator references are created and managed by a named allocator. An allocator reference can only ever point to a complete, allocated object. Allocator references preserve their allocator as part of their type information. Unlike borrowed references, the lifetime of allocator references is not constrained to the lexical scope where they are created.

As with borrowed references, an allocator reference is typically used as a stand-in for the object it references. Strict safety rules ensure that such references always refer to a valid object of the right type.

Allocator Reference Type Declarations

Like borrowed references, use the & operator to declare an allocator reference. It is followed by the reference's allocator, permission (optional), and the value type of the object being referred to.

// reference's allocator is gc and permission is mut. Object's value type is Coord
imm ptr1 &gc mut Coord

Specifying the allocator is what distinguishes an allocator reference from a borrowed reference. The uni permission is the default given to an allocator reference when none is specified. This is the universal donor permission, allowing mutation and inter-thread movement, but restricting aliasing to a single live reference.

Due to type inference, most variable declarations won't need to declare the allocator reference type information. However, function signatures require explicit declaration of allocator references used as parameters or return values.

Allocator Reference Creation

Creating an allocator reference looks a lot like a declaration, except instead of just specifying the value type, one specifies an appropriately typed literal or constructor. For example:

imm rcref = &rc mut Point{x: 4f, y: 3f}

Instead of a literal or constructor, one can also specify another borrowed or allocator reference. This effectively copies (clones) the value held by the second reference:

imm rcref = &rc anotherref

Allocator Examples

Cone supports a wide variety of allocators, each of which varies in how they manage and determine when to free the memory used by the values they allocate. Ultimately, the following flavors of allocators will be possible:

Many variations and combinations of the above mechanics are possible, such as rc allocators that use tracing to break cycles, multi-threaded garbage collectors that use tracing for intrathread memory management and ref counting for inter-thread memory management.

Custom Allocators

New allocators may be created and used. Definition of a new allocator looks and acts very much like a struct with methods. For example:

// A simple reference counting allocator
alloc rc
  refcnt usize

  fn allocate(size usize) *uni rc
    imm rcref = malloc(size) as *uni rc
    rcref.refcnt = 1
    rcref
  fn alias(self *mutx rc)
    .refcnt += 1
  fn dealias(self *mutx rc)
    .refcnt -= 1
    if .refcnt == 0
      .free()
  fn free(self *mutx rc)
    free(self as *u8)

As this example shows, an allocator typically defines at least these four methods which are invoked automatically by the compiler based on key events in the lifetime of an allocator reference:

Any allocator which does not define the alias method (e.g., the lex allocator) ensures single-owner move semantics, as it prevents multiple copies of the reference from being created. The single-owner reference is moved instead of being copied. This restriction also constrains the permissions on borrowed references, effectively disallowing mut and all other shared mutable permissions.

Alternatively, an allocator may also define the alias method which performs no logic (e.g., the gc allocator). In this case, multiple, sharable references to the same value become possible.

Notice also that allocators work with pointers (which have no assigned allocator). All allocator methods are considered wrapped as trust blocks, which allows the use of pointers as well as trusted functions like malloc() and free(). The pointers are automatically and correctly cast by the compiler back and forth from the correct pointer to the correct reference.

_