The basic idea behind references is pretty simple. Instead of having a variable hold a value, we want the variable to hold a reference to some value. A reference is effectively an address to the memory location where the value has been stored. Using this reference, we can access or change the value the reference points to.

Let's illustrate this with a simple example. The & operator obtains a reference to a variable, and therefore to the value it holds. The * (de-referencing) operator can then be applied to the reference to get or change the value pointed to by the reference:

mut a = 3
imm ref = &a    // ref holds a reference to a's contents
imm val = *ref  // val is now 3
*ref = 4        // a is now set to 4

Benefits and Safety Risks

References offer important benefits:

However, without appropriate constraints, the unfettered ability to access and change any arbitrary value based on its address opens up a program to significant safety risks:

Such failures are not always easy to detect and eliminate when the responsibility for being careful lies 100% with fallible humans. Although the consequences are not always serious, they could be catastrophic, especially when malicious actors take advantage of undetected pointer problems to gain access or control over other peoples' privileges or information.

Because of these safety risks, Cone explicitly distinguishes between references and pointers. Although both offer addressable access to values, references are constrained to prevent unsafe use. Pointers are not subject to such constraints, making them more versatile but also potentially more dangerous.

Reference Type Signature

The way that Cone handles references is unusual among programming languages. Although many capabilities are similar to those found in other languages, Cone's distinctive approach optimizes for versatility, safety and ease-of-use.

Getting comfortable with Cone's references begins with an introduction to the four fundamental mechanisms that underlie their capabilities and constraints. These mechanisms are formalized as part of the type signature for every reference.

The following pages provide details on each of these mechanisms. For now, it is helpful to know that the type signature for a reference always begins with & followed by qualifiers for each of the above mechanisms in the listed order. Here are examples of reference type signatures:

imm ref1 &rc imm i32   // ref-counted, immutable reference to an integer value
imm ref2 &i32          // borrowed, 'const' reference to an integer value. Lifetime assumed.
imm ref2 &'a mut i32   // borrowed, 'mut' reference with specified lifetime annotation

Nullable References

By default, references can only point to valid values. However, it is possible to explicitly declare and use nullable references. A nullable reference can have the value null, which means the reference does not point to any valid value.

The type signature for a nullable reference specifies a ? after the ampersand:

imm ref4 &?i32		    	// borrowed, 'const' nullable reference

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

Reference Operations

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 type (particularly its permission and allocator). Reference transfers also check reference type information to ensure everyone is in agreement about what you can do with any passed-around reference.

In general, references are treated as stand-ins for the values they refer to. Operations on references will nearly always apply to the value the reference refers to, rather than the references themselves. However, there are a few operations which do operate directly on references: dereferencing, comparison, and reinterpretation.

De-referencing

As several examples have demonstrated, the * operator is used to access a reference's value. This is called de-referencing. De-referencing a reference is sometimes prohibited based on its permission or whether it might have null as a value.

For most operations on a reference, a reference is automatically de-referenced before performing the operation. For example:

// Assume ref1 is a reference to an integer
imm sum = ref1 + 4        // equivalent to *ref1 + 4

Comparison

Two references may be compared for equality, but only if they have the same type signature

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

It is also possible to compare whether one pointer is greater than another. This is typically only meaningful if both refer to somewhere within the same object.

Reinterpretation

The 'as' operator may be used to take an existing reference and create a new reference with a different type signature. Whenever this might create some safety risks, use of the 'as' operator will need to happen within a 'trust' blocks.

imm newref = oldref as &Point  // Coerce a reference to a borrowed reference

Array and Interface References

In addition to the basic references introduced on this page and explored over the next few pages, Cone supports more complex reference types that carry more than a pointer. These are covered elsewhere:

_