Let’s GO! Part 9: Reference Types 1: Pointers
Reference Types, as the name suggests, are those that are somehow referential. These types are classified as Reference Types in GO:
- Pointers
- Slices
- Maps
- Functions
- Channels
We’ll be talking about each and every one of them on their own parts.
Overview
Pointers are simple variables or arguments (if passed to functions) that hold the address of another variable on the memory. If you’re already familiar with C or C++, you might wanna think about pointers in GO as they are in C and C++. Well that is not false but also not true. The main functionality is the same but they’re somehow different.
How Pointers Differ in GO from C/C++?
Since C and C++ are not automatically garbage collected, it might be a good idea to pass the arguments as pointers in those languages to reduce the overhead of holding the copied values on the memory and prevent memory leaks, and also be faster because the value doesn’t get copied to a new address. But GO is smarter. GO has an automatic garbage collector which only works on the heap
. When you pass the variable as it is, it’ll go on the stack
which will be pushed and popped only. But if you pass your variable as a pointer to another variable it might go to the heap
and may even be slower due the garbage collection process that runs on the heap. Read more about this on this great article.
When to Use Pointers?
While it’s totally up to you either to use them at all or not, because almost anything that can be done with Pointers can simply be done with simple variables as well and it’s even faster most of the times, but there are some cases that you might want to consider using them:
- Passing large structs. The overhead of copying large structs might be worse than the overhead of the garbage collector process that runs on the
heap
. If this is the case, maybe it’s better to use pointers. - Mutability. If you pass a variable as it is as an argument to a function, since the value gets copied to a new address, the function will not be able to mutate the original variable directly in it’s body. If you want to mutate the original variable directly inside the body of your function (“But why? Why would you do that? Why would you do any of that?” -JonTron), you have to use pointers instead. Jokes aside, there will be some cases where you want to do that and you’ll see them in the part about Receivers.
- API consistency. You might want to have an API consistency in your application as some of the Receiver Functions (which you’ll see in the part about Receivers) NEED to get the value by pointer so they can mutate the original value. In this case, having your other Receiver Functions getting their values also as pointers makes your API consistent.
- To signify true absence. Since the default value of pointers is
nil
instead of the original default values of the variables (which you saw at Part 6: Basic Types), they’re useful to know if the value is assigned or not. I mean you might assign0
to a variable of typeint
and you want to know which variables of this type are assigned the value of0
and which are0
by default because no value is assigned to them. In this case using pointers is a good idea. But be careful of the billion dollar mistake.
Again, more info about this on this great article.
Syntax
There are two operators that you need to be aware of before diving into the syntax of Pointers:
- * operator which is called the Dereferencing Operator and it’s used to define pointer variables and access the value stored in the address.
- & operator which is called the Address Operator and it’s used to return the address of a variable or to access the address of a variable to a pointer.
Lesss gooo:
# Either the long syntax
var myPointerName *TYPE
myPointerName = &myVariableName# Or the shorthand syntax
myPointerName := &myVariableName
- The type of your pointer should be as same as the type of the variable that you want your pointer to point to.
- Get the address by the variable’s name with & and get the value by the address with *.
IMPORTANT NOTE: If you log the value of a pointer, you’ll see a hexadecimal number starting with 0x
. That number is the address of your variable on the disk. The note here is that you can store hexadecimal numbers even in simple variables in GO. Every number that starts with 0x
will be considered a hex number. In the Printf
function that comes from the built-in fmt
package, if you format your hex variable with %v
you’ll get the decimal value of it, whilst if you format it with %X
you’ll get the exact hexadecimal value. Try it yourself in your editor.
Let’s see some code:
The output should be:
The address of myVar on disk is: [Some hex number starting with 0x]
The value that is stored in that address is: 23
The address of myVar after mutating should remain the same: [Some hex number starting with 0x]
But the value should change to: 25
The value of myVar after mutation inside the function's body: 12
Note: In the above code, the hex number you’ll get from each iteration of the program for the address will be different.
Yup that’s it! That’s the Pointers! Good job so far. I think it’s too soon to decide either to use pointers at all or not as there are some related things remained to learn that’ll help you decide better while designing the flow of your applications. So just know what the Pointers are and how are they declared for now and we’ll talk about these concepts later on.
Let’s learn about the Slices next.