Note: These are largely implemented, but freezing/lifetime enforcement is missing and they struggle across method calls.

References work differently than other types. The basic idea is simple: references carry an address that points to some value in memory. Using this reference, we can access or change the value the reference points to.

For example:

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

The & operator borrows a mutable reference to the variable a, effectively pointing to the value it holds. The * (de-referencing) operator is used to obtain and then change the value pointed to by the reference.

References are useful, as they support:

As the name suggests, borrowed references point to values that already exist in memory. A common scenario is when we have some existing value and, for some limited lexical scope, we want a reference able to work with that value. A borrowed reference is ideal for this requirement because:

Creating a Borrowed Reference

Here are several ways to borrow a reference to some existing value.

Borrowing from a variable

Place the ampersand operator in front of any variable's name to create a borrowed reference that points to the value held by that variable. This works equally well on any global, local or parameter variable:

imm glovar = 2
fn func(parm i32):
	mut localvar = 3
	imm ref1 = &glovar    // ref1's type: &i32
	imm ref2 = &parm      // ref2's type: &i32
	imm ref3 = &localvar  // ref3's type: &i32

Borrowing from another reference

To explicitly create a borrowed reference out of some other reference, place the ampersand operator in front of a de-referenced reference:

imm borref = &*someref    // Borrow from someref

More often, however, borrowing a reference from an existing reference happens implicitly, as part of a function (or method) call or assignment. This happens when the receiver is given a region-managed reference but expects a borrowed reference. So long as the value type (and permission) match, the borrow is performed automatically.

// This function accepts a borrowed reference
fn incr(nbr &i32):
  *nbr + 1

fn main():
  imm allocref = +so 1   // An owning reference pointing to an allocated value of 1
  incr(allocref)         // Coerces the rc reference to a borrowed reference

Because any reference may be safely coerced to a borrowed reference, functions and methods typically accept borrowed references as parameters. This capability is what makes possible the benefits mentioned earlier:

Referring to a field or array element

A borrowed reference may point to a value within some composite value, such as a field in a struct or an element in an array. Again, all we need to do is apply the ampersand operator:

imm ref1 = &apoint.x   // a field within a struct
imm ref2 = &vec[a]     // an element within an array

This kind of internal borrow is quite powerful, as it supports:

Pattern-matching and each loops

One may obtain borrowed references during each iteration or pattern matching by using a borrowed reference with the required permission as the source:

// increment every number in list
each x in &mut list:
  ++*x   // x is a mutable reference

Permissions

A reference permission may be specified when a reference is borrowed. The permission governs what may be done with the reference: May we read its value? May we change its value? May we make a copy of the reference?

This permission is specified after the ampersand operator. If none is specified, ro is assumed.

mut a = 3
imm ref = &mut a   // Creates a mutable borrowed reference to a's value
*ref = 4           // a is now set to 4

The permission requested for the borrowed reference must be allowed by the source we are borrowing from. For example, a mutable borrowed reference may not be obtained from an immutable variable.

Permission mechanics are rich enough to be more fully covered on their own page.

Lifetime Constraints

Every borrowed reference has a lifetime constraint implicitly imposed on it. This lifetime constraint indicates how long the borrowed reference can stay alive before it must expire.

This example demonstrates the importance of this lifetime constraint to memory safety:

fn getval() i32:
  mut ref &i32
  {
    imm a = 5
    ref = &a  // Bad idea! This violates the lifetime constraint
	}
  *ref   // Oops!

The scope of the variable a lasts only for the duration of the inner block. So when we are done executing that block, the variable a ceases to exist. However, when we try copying the borrowed reference into ref, we effectively extend its life beyond the scope it was created in and beyond the life of the value it points to. When we then dereference it, we are trying to retrieve a value that is no longer there. That's a memory safety hazard we must avoid.

The compiler protects against this by throwing an error on this line: ref = &a. This is illegal because we are storing a borrowed reference into a memory location whose lifetime is longer than the borrowed reference. A borrowed reference may only be stored in a place whose lifetime is the same or smaller than the lifetime of the borrowed reference.

Most of the time, the compiler has all the information it needs to infer and enforce lifetime constraints. However, sometimes it needs help in the form of lifetime annotations.

Freezing access to the source of a borrow

When a borrowed reference is created, its source is always a named variable. That source variable either holds or points to the value we are borrowing a pointer to. When we borrow from the source, we are not just gaining access to the value, we are also gaining some or all of its access permissions.

Whenever we borrow from a non-global source, the source variable is made inaccessible for the lifetime of the borrowed reference in order to preserve permission guarantees:

fn freeze(n i32) i32:
  if n > 1:
    imm borref = &n   // borrowing makes 'n' inaccessible in this block 
    imm m = n         // oops! n is frozen and may not be accessed
  n                   // Ok because the borrow has expired.

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.

mut count = 3
imm ref = &mut count
imm val = *ref  // val is now 3
*ref = 4        // ref now points to the value of 4 (in count)

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

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 &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 y = ref.y // equivalent to: (*ref).y

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

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 ?&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.

Array and Virtual References

In addition to regular references, there are also special-purpose "fat" references which are handled somewhat differently:

_