Pointers
Overview
Let's first check how variable are represented in the memory:
Value | 0 | 0 | 0 | 10 | 1 |
---|---|---|---|---|---|
Address | 1 | 2 | 3 | 4 | 5 |
Variable | x | y |
x
is a 32bit integer, which needs 4 bytes (4*8) to store the integer in the memory.
y
is a boolean and needs just 1 byte to store it in the memory.
For every byte in the memory there is an address, so the programm can read or write the specific values into the memory.
A pointer "points" to a specific address of a variable. Let's check that:
Value | 0 | 0 | 0 | 10 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 5 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Address | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Variable | x | y | pointer X | pointerY | pointerZ |
The &
is the address operator
.
It returns a the address of the memory location where the value is stored.
The *
is the indirection operator
. It returns the actual value of a pointer.
This is also called dereferencing.
If you dereference a pointer which is nil, you will get a panic:
outputs:
true
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47dd2d]
There is a builtin function new
, which creates a pointer type variable.
It returns a pointer to a zero value of given type.
For structs you can use just a &
while initializing one:
Take care, that you cannot take the address of a constant. Check here:
output:
You can use two solutions here. First introduce a variable of the constant and then take the address of it. Or create a function that gets a type and returns the pointer to that type.
output:
Mutable Parameters or Call by Reference
Go is a call by value language, which means that if you pass a variable into a function as parameter, it will make a copy of that variable. So if you try to manipulate the passed in variable you will only do changed on a copy of the actual passed in variable. But what if you store an address of a variable and pass in the address into a function? -> Then you will be able to manipulate the actual value of the outer variable. Let's check an example:
output:
But it has some implications, which could be not self explanatory. For example, if you have a nil pointer and you want to assign a variable to this pointer. You still work on a "copy" of that pointer.
This can be fixed by derefencing the value and setting it. By dereferencing we access the actual value in the memory and overwrite it within a function.
Danger
Please be carefull with dereferencing, because they can panic. Therefore always check for nil pointer!
Fixed:
Passing Pointers rather than Values
Surely passing pointers and modifying their values is easy. But it's actually an "anti-pattern" to make functions, which receive a pointer and modifying the value of your variable. Modern Software Engineering teaches us to work with immutable values rather than mutable ones Source.
Therefore it's better to make functions which receive a copy of a value, mutate it and returns the mutated value:
Performance
If you pass a variable into a function, the whole variable gets copied to work on it. So if you pass in a variable which is around 10megabytes big, it can take up to 1 millisecond to copy the variable. Beside that it takes only about 1 nanosecond to load a pointer into a function.
But returning a pointer can take more time than returning a variable. But only in one case, if you variable is smaller than 1 megabyte. For example for a 100 byte pointer it takes 30 nanoseconds and to return a value, it takes 10 nanoseconds. Once your data is bigger than 1 megabyte, this rule inverts.
So for the vast majority of cases you should use call by value, only in a few cases a pointer makes sense.
Zero Value vs. No Value
A common usage for pointers is to set a variable either to it's zero value or to set it to nil. If you need to explicitly say, that a variable is not set, use a nil pointer.
For example if you need optional parameters:
Maps and Pointers
If you pass a map into a function you can manipulate the actual value of the map. This is because Go doesn't copy the value of the map but passes a reference (a pointer to struct) into the function.
Therefore you should avoid using maps, unless you are working with really dynamic JSON data for example. Especially if you design your code to work in a team, it is better to define a concrete struct for your data structure than to use a dynamic map.
When to use Methods over Functions
Any time your logic depends on values that are configured at startup of changed while your program is running, those values should be stored in a struct and that logic should be implemented as a method.
Follow this three rules and you'll be fine:
- when implementing methods of an interface for your struct (we will do interfaces in the next chapter)
- when the function needs to use a private variable within your struct
- when the function is completely related to the struct