The first rule of pointers is: Don’t be afraid of pointers. If you are used to Java, JavaScript, Python, or Ruby, you might find them intimidating, but pointers are actually the familiar behavior for classes. It’s the nonpointer structs in Go that are unusual.
In Java and JavaScript, primitive types and classes behave differently. (Python and Ruby don’t have primitive values, but use immutable instances to simulate them.) When a primitive value is assigned to another variable or passed to a function or method, any changes made to the other variable won’t be reflected in the original, as shown in the Java code in Example 5-1.
Example 5-1. Assigning primitive variables doesn’t share memory in Java
int x = 10;
int y = x;
y = 20;
System.out.println(x); // prints 10
However, let’s take a look at what happens when an instance of a class is assigned to another variable or passed to a function or method. (The code in Example 5-2 is written in Python, but you can find similar code for Java, JavaScript, and Ruby in the examples/language_pointer_examples directory.)
Example 5-2. Passing a class instance into a function in Python
class Foo:
def __init__(self, x):
self.x = x
def outer():
f = Foo(10)
inner1(f)
print(f.x)
inner2(f)
print(f.x)
g = None
inner2(g)
print(g is None)
def inner1(f):
f.x = 20
def inner2(f):
f = Foo(30)
outer()
Running this code prints the following output:
20
20
True
That’s because the following scenarios are true in Java, Python, JavaScript, and Ruby:
-
If you pass an instance of a class to a function and you change the value of a field, the change is reflected in the variable that was passed in.
-
If you reassign the parameter, the change is not reflected in the variable that was passed in.
-
If you pass nil/null/None for a parameter value, setting the parameter itself to a new value doesn’t modify the variable in the calling function.
Some people explain this behavior by saying that class instances are passed by reference in these languages. This is untrue. If they were being passed by reference, the second and third scenarios would change the variable in the calling function. These languages are always pass-by-value, just as in Go.
What you are seeing is that every instance of a class in these languages is implemented as a pointer. When a class instance is passed to a function or method, the value being copied is the pointer to the instance. Since outer and inner1 are referring to the same memory, changes made to fields in f in inner1 are reflected in the variable in outer. When inner2 reassigns f to a new class instance, this creates a separate instance and does not affect the variable in outer.
When you use a pointer variable or parameter in Go, you see the exact same behaviors. The difference between Go and these languages is that Go gives you the choice to use pointers or values for both primitives and structs. Most of the time, you should use a value. Values make it easier to understand how and when your data is modified. A secondary benefit is that using values reduces the amount of work that the garbage collector has to do. I’ll talk about that in “Reducing the Garbage Collector’s Workload”.