In some programming languages, a reference is an alias to an existing value. Compared to pointers, they’re safer since the compiler can type check, but are generally less powerful because we can’t type cast.

The variable a reference points to cannot be reassigned, but the value held can be changed. References can only be assigned at initialisation and can’t be assigned to nullptr or no value. They also don’t have a separate memory location.

int a = 8, b = 17;
int& c = a; // general form of references
a == b; // >> FALSE
c = b; // valid
a == b; // >> TRUE

Major applications of pointers include passing and returning by reference. We also use them as shortcuts in our code — if we have structures within structures, we get fairly long code to access members, and we can skip this using a reference.

In practice, references are abstracted and are actually implemented beneath the hood with pointers.

References are supported in C++ and Rust.

Pass and return by reference

When we pass by reference to a function, it takes the argument directly. We can perform manipulations without the need of pointer arithmetic, and we also avoid the overhead of passing by value (i.e., making a memory copy). An implication of this is that when we manipulate a reference, the variable it is an alias of will also change, i.e., there is no separate memory location.

void bumpMark (int& mark) {
	if (mark < 90)
		mark += 10;
}
 
int main () {
	int mark = 64;
	bumpMark(mark); // no need for extra ampersand, etc. here
}

Especially when we look at methods that return *this, we can also return by reference. For instance, we may not necessarily want to return a copy of the object, and actually return the exact object.

C++-specific

Vector of references

This actually can’t work. Containers (like vectors) must be assignable, so references are incompatible with vectors. Similar non-assignable types aren’t allowed, like vector<const int>.

Rust-specific

We can also use references to avoid transferring ownership of variables. One notable difference is that we also need to create a reference in the function call (&name) in addition to the function signature to support references. References fit in the ownership model by doing “borrowing”: the new scope doesn’t own the memory and it returns it when done.

We can also have a mutable reference with the mut specifier (this change needs to be reflected in the function signature) if the value needs to be changed. Note that you can only have a single simultaneous mutable reference to a value.

This prevents data races, where more than one pointer accesses the same data at the same time and we get a write conflict, with no mechanism to synchronise access to the data.

Note also that you can’t have a mutable reference while an immutable reference exists at the same time.