References and Values

Someone was wrong on the Internet, so I posted a comment. Cross-posting here so I don’t lose the text. Quoting myself:

We need to pay attention to the difference between references and pointers, since that seems to be the root of the confusion. These two words don’t mean exactly the same thing in different languages. As a compiler developer, I know these things can be tricky for humans. I’ve seen more than a few bugs caused by misunderstanding all of this.

Pointers are variables that can contain the value of an address. You can update that value to be anything. Set a pointer to the number 15. Or more commonly, set it to 0, which many languages including Java grant a special name — null. You can do arithmetic on the value contained in a pointer, like p = p + 5. Pointers can contain invalid addresses. Java lets you update variables containing addresses (a = new Foo(); a = new Bar();), and it lets you set them to invalid addresses like 0 (a = null). Java object variables are pointers. The Java compiler doesn’t permit these pointers to be arithmetically manipulated (a = new Foo(); a = a + 5;), but that’s a limitation overlaid on what are, intrinsically, pointers.

References are a very special kind of pointer, because the compiler puts strict rules on them and grants them super-powers with certain operators. The most important difference: references cannot point to arbitrary addresses, like null. They must always refer to an actual object. And references are fixed once initialized — once they refer to some object, they can never refer to another. These two properties are fundamental to references, though some languages vary slightly in their definition.

So what good are references, with all these restrictions? Good question — it gets to the heart of the debate. Because references can never be updated, and because they’re always valid, the compiler can change the meaning of assignment and data reference. Remember how with pointers, when you assign to them, you change the pointer itself?

int b = 10;
int *p = &b;
p = 5;
print(p, b); // prints 5, 10

Now p contains the value 5, which is an invalid address. But no matter — all we’ve done is update the value of p. b is unchanged. This is by-value assignment. Now look at by-reference assignment:

int b = 10;
int &r = b;
r = 5;
print(r, b); // prints 5, 5

Because of the special properties of references, the meaning of the assignment operator is different. Under the covers, both pointers and references are addresses. The words “pointer” and “reference” imply compiler features are at work — pointer arithmetic and index operators in the pointer case, modified assignment and null guarantees in the reference case.

Call-by-value and call-by-reference follow the same logic:

void call_by_reference(int &r) { r = 6; }
void call_by_value(int *p) { p = 6; }
void pass_pointer_by_reference(int *&pr) { pr = 6; }

int i = 42;

call_by_reference(i);
print(i); // prints 6

call_by_value(&i);
print(i); // still prints 6 — we passed a pointer, but the function only changed its local copy

int *foo = &i;
pass_pointer_by_reference(foo);
// now foo is an invalid pointer, because the reference was assigned-to
// inside the function. i is unchanged.

So what is Java? It implements pointers and a by-value calling convention. A pointer-heavy, call-by-value language. I think that’s patently obvious once you see the distinction.

The core insight here — that references change the meaning of the = operator itself — is the thing most people miss. They think references are “just pointers with restrictions.” The restrictions are the whole point. They’re what let the compiler give = a different semantics. Take away null, take away reassignment, and suddenly assignment means “write through to the thing I refer to” instead of “update which thing I point at.”

Java muddied this for a generation of programmers by calling its pointers “references.” They’re not. They’re nullable, reassignable pointers with by-value calling conventions. The fact that you can’t do arithmetic on them doesn’t change what they are — it just makes them safer pointers.