Borrowed references offer a straightforward introduction to references. Remember this example from the previous page?

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

The &a on the second line creates a borrowed reference. Borrowed references point to a value which has already been allocated in some other way. Thus, they are "borrowing" the pointer address from something that already knows where the value is stored. Unlike allocated references, borrowed references don't know how the value was created nor do they manage its memory existence in any way. Instead, borrowed references simply have a known, limited lifetime, guaranteed to last no longer than the reference they borrow from or the value they point to.

This page describes how borrowed references are declared, shows how they are created and used, and explains the lifetime mechanisms that help ensure borrowed references stay memory safe.

Borrowed Reference Declarations

An explicit type declaration for 'ref' in the earlier example would look like this:

imm ref &i32 = &a

The type declaration for a reference always begins with &. What follows can be up to four pieces of information which correspond to the reference mechanisms mentioned on the previous page. These are specified in this order:

To summarize, borrowed reference type declarations always need to specify the value type. However, only references that want to mutate the value will need to specify a permission. Far rarer are function signatures that require lifetime annotations on parameter or return references.

Creating a Borrowed Reference

There are several ways to create a borrowed reference.

From a variable

As already demonstrated, one may borrow a reference to any global, local or parameter variable:

imm glovar = 2
fn func(imm parm)
	mut localvar = 3
	imm ref1 = &glovar
	imm ref2 = &parm
	imm ref3 = &localvar

The permission of the borrowed reference will match that of the variable it borrows the address to. Thus, ref3 is a mutable borrowed reference, usable to change the value it points at. An alternative permission may be specified after the &, if a more constrained permission is desired for the borrowed reference.

Notice that the variable 'ref3' that holds the reference is itself immutable (it can only ever refer to 'localvar'). However, it holds a reference able to change (mutate) 'localvar'. In this statement, 'imm' prohibits the variable 'ref3' from being changed, whereas the implicit 'mut' allows the value pointed at by ref3 to be changed. These are separable concerns.

From another reference

Any kind of reference (including an allocated reference) may be coerced into a borrowed reference. This sort of coercion commonly happens when passing any reference to a function or method that expects a borrowed reference:

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

fn main()
	imm allocref = &rc 1   // An allocated reference pointing to an allocated value of 1
	incr(allocref)         // Coerces the allocated reference to a borrowed reference
	// allocref now points to the value 2

Because any reference may be safely coerced to a borrowed reference, functions and methods typically accept borrowed references as parameters. This offers two huge benefits:

From a substructure

One can borrow a reference to a value contained within a compound value, such as a named property of a struct or an element or slice in an array.

imm ref1 = &apoint.x   // a property within a struct
imm ref2 = &vec[a]     // an element within an array
imm ref3 = &vec[3].point.x

This technique applies whether the variable holds the compound value or holds a reference to one.

From a function or method

A reference may be borrowed from any named function or method. That reference may be used to call the function it refers to.

fn incr(x i32) i32
  x + 1

fn caller()
  imm fnref = &incr   // Get a borrowed reference to a function
  fnref(4)            // Call incr and return 5

Using a similar technique, it is also possible to make use of anonymous functions:

fn caller()
  imm fnref = &fn (nbr i32) i32 {nbr+1}
  fnref(4)            // Return 5

Note, it is not possible to dereference a borrowed function reference using the '*' operator. Its permission forbids that. All you can do with a function reference is pass it around and use it to call the function it points to.

Reference Use (Dereferencing)

Use of a borrowed reference typically focuses only on the value it references. The * dereferencing operator may be used to handle this explicitly. Within expressions, dereferencing retrieves the value the reference points to. Used to the left of an assignment operator, dereferencing changes that value.

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

It is not always necessary to explicitly dereference a borrowed reference to use it, since a reference is understood to be a stand-in for the value it references. Dereferencing happens implicitly when using the . operator to access a property or call a method on some value pointed to by a reference:

mut a = -4
imm ptr = &mut a
imm y = ptr.abs    // y is 4. Similar to: (*ptr).abs

With method calls, it is important to note that self receives the the reference rather than the value.

Reference Lifetimes

At last we get to what can sometimes be a frustrating aspect of borrowed references: lifetime constraints. Their purpose is to protect against a borrowed reference pointing to a value that no longer exists. Consider:

fn getval() i32
	mut ref &i32
	do
	  imm a = 5
	  ref = &a
	*ref   // Oops!

The scope of the variable 'a' lasts only for the duration of the do block. So when we are done executing that block, the variable 'a' ceases to exist. However, 'ref' (which points to 'a') continues to exist, but now points to a value that is gone. When we dereference it, we are problematically trying to retrieve a value that is no longer there. The purpose of lifetime reference constraints is to prevent this from ever happening.

Lifetime constraints forbid a reference from pointing to a value whose lifetime is shorter than its lifetime. Within a single function, this is accomplished by comparing when their respective scopes end. If the scope for a value happens sooner than any reference trying to point to it, Cone will simply not allow changing the reference to point to that value. Thus, the line ref = &a will trigger a compile error, since the do block that the 'a' value belongs to ends before the function block that 'ref' belongs to.

Lifetime Annotations

Within a function, Cone has all the information it needs to enforce lifetime constraints. That is why references typically do not need to declare lifetimes. However, when dealing with borrowed references that travel to and from functions, Cone may not know enough about the lifetimes of these references to correctly enforce lifetime constraints. In these cases, lifetime annotations are required on parameter or return references to close this gap.

The way to think about it is that there is a thick brick wall that separates a called function from the function that calls it. Each cannot see anything about what the other is doing with references. The purpose of the lifetime annotations on function signatures is to establish a contract that summarizes the lifetime relationships between parameter and return borrowed references which Cone can then enforce independently on both the caller and the callee. These lifetime annotations do not tell us everything about lifetimes, only what is needed to correctly enforce lifetime constraints.

Lifetime annotations only apply to borrowed references (not allocated references). Furthermore, if none of the borrowed references can change a value and if no borrowed reference is returned from the function, no lifetime annotations are required. Really, there are only two circumstances where lifetime annotations might be required, both of them requiring that the function receives multiple parameters that contain borrowed references.

Returned borrowed references

If a function accepts multiple borrowed references and returns value(s) that contain borrowed references, the brick wall prevents us from knowing which of the borrowed reference parameters is the source for the returned borrowed reference. That means the caller does not know what the lifetime is of the returned borrowed references. Without that knowledge, how can we safely enforce lifetime constraints on wherever the caller might store that reference? Consider this classic example:

fn refswitch(nbr i32, ref1 &i32, ref2 &i32) &i32
	if nbr < 0
		ref1
	else
		ref2

fn caller(nbr i32)
  mut ref &i32
  imm outer = 5
	do
	  imm inner = 10
	  ref = refswitch(1, &outer, &inner) // Is this safe???

To play it completely safe in the absence of lifetime annotations, Cone will assume for the callee that the lifetime for all references is the same. However, with the caller it will assume the returned reference might have the longest lifetime of the passed references. Using those rules, the above program would compile with an error in the caller, since the lifetime of one of the parameters is shorter than the lifetime of ref. The only way to call refswitch safely is when the lifetime of both references are at least as long as the lifetime of ref.

Lifetime annotations may be used to relax this conservative stance. Marking multiple references with the same simple lifetime annotations signals that the lifetimes of these borrowed references should be treated as if they were the same (in the most conservative way possible). Consider:

fn refone(ref1 &'a i32, ref2 &i32) &'a i32

Using lifetime annotations ('a), this function declares that the caller should treat the lifetime of the returned reference as the same as the lifetime for ref1. It also enforces that the callee's logic provides no way to return a reference sourced from ref2. Thus, a called function should annotate some references with lifetime annotations when it knows it implements no dependences between a returned reference and some parameter reference(s).

Mutable borrowed reference parameters

If a function accepts multiple references, at least one of which is mutable, we have an opportunity for a different sort of lifetime problem. If the called function changes the value pointed at by the mutable reference to a value retrieved from a different reference parameter, it has insufficient information to know that lifetime constraints are safely enforced.

fn refchange(refmut &mut i32, refval &i32)
	*refmut = *refval
	
fn tryit()
	mut a = 5
	do
		imm b = 10
		refchange(&a, &b) // Oops!

In the absence of lifetime annotations, Cone will once again apply the most conservative assumptions. The callee will assume their lifetimes are the same. The caller will assume the known lifetime of all mutable reference can never be longer than the known lifetime of any non-mutable reference. The use of lifetime annotations can also be used in these circumstances to relax these assumptions, when the called function can guarantee there are no lifetime dependences between some of the references.

'static lifetime

The lifetime of a global variable is forever. The same is true for all functions, methods and anonymous functions. Any reference created directly from a global variable or function is implicitly marked with the 'static lifetime annotation. A reference may always safely point to a value that is known to be global, as this never violates lifetime constraints.

References to functions preserve this 'static lifetime even when a function reference is passed from one function to another. This means that function references can always be moved around freely and safely between functions and safely stored and used anywhere, without the need to explicit annotate them with 'static.

However, that is not true for global variables when references to them are passed around and stored. Unless the receiver reference (and any donor reference) are explicitly marked with 'static, lifetime shortening and constraints will apply.

_