programmingconcepts

§ 5.2 · Memory

Pointers and references

A reference (or pointer) is a value that says where some data lives instead of holding the data itself, so two variables can share one object.

A pointer or reference is a value that records where data lives rather than holding the data itself. Copying a reference shares one object, so a change made through either variable shows up in both. Python, JavaScript and Java pass references by value: a function can change your list but cannot make your variable point elsewhere.

Copying a reference
Shares the object
Copying a value
Makes it independent
Passing to a function
Copies the reference
Explicit pointers in
C, C++, Go, Rust
Requires
Stack vs heap
def update(nums, n):
    n = n + 1
    nums[0] = n
    nums = [0, 0]


def demo():
    count = 5
    a = [1, 2]
    c = a.copy()
    update(a, count)
    b = a
    return count, a, b, c

Call demo(). Its frame starts empty.

step 0 / 0running framereference usedjust writtennothing refers to it

Fig. 5.2. Stack frames on the left, lists on the heap on the right; each arrow is a reference. Blue marks a reference being copied or followed, orange a value just written, and a faded list is one nothing refers to any more.

Intuition

A house and its address. Writing the address on two sticky notes doesn’t build a second house: anyone who follows either note arrives at the same door, and if one of them paints the door red, the other sees red too. But if you cross out one note and write a different address on it, the other note and the house are unchanged.

A variable that holds a reference is the sticky note. The object on the heap is the house.

How it works

Some values are stored directly in a variable: numbers, booleans, and in some languages small structs. Copying one gives you an independent copy.

Lists, objects, maps and strings usually live on the heap, and the variable holds a reference to them. Copying that variable copies the reference, so both variables point at the same object.

In the figure:

  1. count = 5 stores the number in demo’s frame.
  2. a = [1, 2] creates a list on the heap; a refers to it.
  3. c = a.copy() creates a second list with the same values.
  4. update(a, count) copies the reference into nums and the number into n.
  5. n = n + 1 changes only update’s copy of the number.
  6. nums[0] = n follows the shared reference, so demo’s a sees the 6.
  7. nums = [0, 0] points nums at a new list. a is unaffected, and once update returns, nothing refers to the new list.

In each language

Language How references work
Python Every variable holds a reference. Numbers and strings can’t be changed in place, so sharing them never surprises you; lists, dicts and objects can. is compares references, == compares values.
JavaScript Numbers, strings and booleans behave like values; objects and arrays are shared by reference. === on two objects is true only if they are the same object.
Go Structs and arrays are copied on assignment. Slices, maps and channels hold a pointer inside, so copies share data. Explicit pointers: p := &x, then *p = 3.
Rust &x borrows to read and &mut x borrows to change. The compiler allows many readers or one writer at a time, never both, and a reference can’t outlive its value.
C Pointers are addresses you can do arithmetic on: int *p = &x; *p = 3;. Nothing stops a pointer to freed memory or NULL being used, so mistakes crash or corrupt memory.
Java Primitives (int, double) are values; every object variable is a reference. Arguments are always passed by value, and for objects the value is the reference.

Common mistakes

  1. Expecting b = a to copy a list. It copies the reference. Copy explicitly: a.copy() or list(a) (Python), [...a] (JavaScript), slices.Clone(a) (Go), a.clone() (Rust).
  2. Shallow copies of nested data. Copying a list of lists copies the outer list only; the inner lists are still shared. Use copy.deepcopy (Python) or structuredClone (JavaScript) when you need a full copy.
  3. Python’s mutable default argument. def add(x, items=[]) creates the list once, when the function is defined, so every call shares it. Use items=None and make a new list inside.
  4. Null and dangling pointers. Following a null reference crashes (NullPointerException, TypeError: Cannot read properties of null); in C, using a pointer after free is undefined behaviour.

Check yourself

Check yourself

Inside a function, nums[0] = 6 and then nums = [0, 0]. What does the caller's list look like?

Check yourself

a = [1, 2], b = a, c = a.copy(), then a.append(3). Which lists have three items?

Sources

  • Python documentation, “Programming FAQ: How do I write a function with output parameters (call by reference)?”
  • The Go Programming Language Specification, sections “Slice types” and “Address operators”.
  • The Rust Programming Language, chapter 4.2, “References and Borrowing”.

Newsletter

One new concept each week, by email.

Signup opens soon.