Note: Much of this capability is implemented except for error handling on bad allocations.

Imagine that a program's memory is conceptually partitioned into regions, each with its own strategy for allocating and reclaiming memory for values. Every program value is wholly located within and governed by its owning region.

By way of illustration (even though we have not said so until now), the memory space for every variable's value is automatically allocated in one of these regions:

These are not the only kinds of regions supported by Cone. A broad range of region strategies may be used to dynamically allocate space for new values, such as &rc (reference counting), &gc (tracing GC), &ar (arena) and &so (single owner). The next page describes and evaluates different region strategies in more detail.

Before we get to that, let's focus first on owning references, the handles we use to work with values dynamically allocated by regions. All owning references point to some valid, allocated value. This page describes how owning references are created and used.

Creating Owning References

Let's ask a region to allocate a new value and give us back an owning reference:

imm nbrref = &rc imm 4

This uses the ampersand operator to dynamically allocate and initialize a new value, specifying:

What we get back from this allocation is an owning reference, which this example stores in the variable nbrref. The inferred type of this reference looks similar to the allocation: &rc imm i32. This type signature includes the type of the initial value as the reference's value type.

A type's constructor or initializer may be specified as the initial value.

imm gcref = &gc mut Point3[1f, 2f, 3f]  // Allocate a new Point3 value using a constructor

Creating Multiple References to the Same Value

The creation of the first owning reference to a new value always comes from an allocation, as shown above. Additional owning references to that value may then be created by copying an existing reference via assignment or function calls:

imm nbrref = &rc imm 4
imm nbrref2 = nbrref        // nbrref2 and nbrref point to same value

How References and Allocations Expire

Owning references keep the value they point to alive. So long as a value has at least one owning reference that points to it, the value's region will not dispose of the value and reclaim its memory space. Only when all references to a value expire does the region have the right to free the value (although how quickly this happens varies by region).

So, when does the lifetime of an owning reference expire? When the data structure it is bound to expires or changes its value. If the reference is never stored anywhere, it is consumed by the expression it is part of and expires nearly immediately. If the reference is stored in a variable, it expires no later than the end of the lexical scope that variable is declared within (and sooner, obviously, if the variable takes on a new value). If the reference is pointed at by another reference, then the lifetime of the former depends on the lifetime of the latter.

When all references to an allocated value expire and the region has decided to reclaim the value's space, the region will automatically invoke the value's finalizer, if it has been defined.

Reference Operations

The de-referencing and comparison operators may be directly applied to references.

De-referencing

The most common use of references is to access and work with the value it points to. Accessing this value is called de-referencing. To explicitly de-reference, use the * operator. Within expressions, dereferencing retrieves the value the reference points to. Used to the left of an assignment operator, dereferencing changes that value.

imm ref = &rc mut 3
imm val = *ref  // val is now 3
*ref = 4        // ref now points to the value of 4

De-referencing is only permitted if the reference's permission allows it. Certain permissions, such as opaq and the locked permissions, disallow de-referencing.

Similarly, de-referencing to obtain a value is prohibited if move semantics apply. This would happen if the reference uses the uni permission or points to a value of a non-copyable type. This prohibition is because we likely have no way to comprehensively deactivate the source of this value. To get around this limitation and extract the value anyway, swap the value the reference points to with some other valid value.

Comparison

Two references may be compared for equality, even if they do not have the same type signature. What this test determines is whether both references point to the same value:

// Do ref1 and ref2 point to the same value?
if ref1 == ref2:
		// do some stuff

It is also possible to compare whether one reference is "greater" than another. This compares their respective memory addresses. This might only be meaningful if both refer to somewhere within the same object.

Reference Handling

References are values. As such, they can be stored and passed around a program. Whether such transfers are simple copies or moves depends on the reference's permission (move semantics apply to all references having the uni permission). Reference transfers also check reference type information to ensure everyone is in agreement about what you can do with any passed-around reference.

Even though references are typed values, they do sometimes get some special treatment to make them more convenient to use, particularly to enable them to act as stand-ins for the values they point to.

Reference Method Definitions

Methods may be defined that accept references as the type for self. However, importantly, such methods must be defined as part of the value type's collection of methods. Thus, if we want to define a method that works on a reference to a Point, that method must be defined within the collection of methods for the Point type:

struct Point:
  // .. other Point methods here

  // method that accepts a reference to a Point
  fn normalize(self &rc mut Point):
    imm len = self.len
    x /= len
    y /= len
    z /= len

Implicit De-referencing

Since a reference is generally understood to be a stand-in for the value it references, de-referencing often happens implicitly. For example, when the dot operator is applied to a reference to access a field, the reference is automatically de-referenced:

imm ref = &rc mut Point[1f, 2f, 3f]
imm y = ref.y // equivalent to: (*ref).y

Automatic de-referencing might also happen when applying a method to a reference. For example:

imm ref = &rc mut -4.
imm pos = ref.abs    // pos is 4. Equivalent to: (*ref).abs
imm sum = ref + 4.   // sum is 0. Equivalent to *ref1 + 4.

With methods, the decision on whether or not to implicitly de-reference depends on what is supported by implemented methods. If an existing method can accept the reference as a reference, it is used. If not, the reference is de-referenced before applying the method.

Nullable References

References only ever point to valid values. However, it is possible to explicitly declare and use nullable references, much like any other nullable value.

imm ref4 ?&rc imm i32		 // nullable reference to an integer

In addition to pointing to a valid value, a nullable reference may also have the value Null, which means the reference does not point to any valid value. To ensure safety, access to a nullable reference's value is only possible if the code first ensures the reference does not have the value of Null:

// This condition is true only if maybePoint is not null ...
if maybePoint:
  imm point = *maybePoint // ... allowing us to obtain its value
imm point2 = *maybePoint    // **ERROR** We don't know if maybePoint is null here

Note: A special optimization ensures that nullable references are the same size as regular references.

Memory Allocation Errors

Although unlikely, it is still possible for memory allocation requests to fail. This typically happens when not enough contiguous, free memory exists to satisfy the request.

Failure to allocate memory will trigger a different response depending on whether the owning reference is marked as nullable.

_