Pointers #
One of the first questions you will inevitably stumble upon is when to pass/return a value and when to return a pointer to it. Java and C# have simplified things a lot by making everything an Object
reference; thus, you don’t have to make the mental jump in most cases - it is just a pointer under the hood. This design decision does not come for free - as a Java developer, you are undoubtedly aware of how much memory can be used for powering even the tiniest Java application. The runtime quickly expands the necessary memory capacity by storing everything on the heap. It is up to the garbage collector to regularly clean unused references to prevent an application from exhausting the hardware’s available RAM.
Being closer to the metal, Go has left this decision in your hands. You can use pointers in all the complex scenarios you know from Java and C#, but drop to passing and returning values (value copies) around where appropriate. The result is a healthy balance between the two, reducing both the memory required by the application to work and the burden on the garbage collector.
When writing Java code, one is aware of the performance implications of passing pointers around, but since that’s pretty much what the language allows, one could quickly shove these thoughts away. In Go, one might feel compelled to make a firm decision - what do we copy around, and what is it better to pass as a pointer? That is, if I have to be even more blunter than that - when it is necessary to precede a variable’s type with a * and when it is fine to just leave as is.
Let’s try to come up with some heuristics:
Primitives #
Although one can turn any type in Go into a pointer type, with some exceptions, we can say that in 80% of the cases, it is fine to leave built-in types as they are. This means that all primitives will be copied around when passed as function arguments, or returned from functions.
// playground: <https://go.dev/play/p/wSloBNm65Do>
func main() {
var res = timesTwo(2)
fmt.Println(res)
}
func timesTwo(in int) int {
return in * 2
}
Sure, you can turn them into pointers and change them inside the function, but that really leads to a more difficult to comprehend syntax.
// playground: <https://go.dev/play/p/_psdrUXJ9pV>
// Avoid it, unless really necessary
func main() {
var res = 2
timesTwo(&res)
fmt.Println(res)
}
func timesTwo(in *int) {
*in = *in * 2
}
Chances are, if you want to modify a primitive value, it will sit somewhere inside a struct. You’d rather pass a pointer to the struct, rather to the primitive itself.
More content is coming soon …
Not all pointers were created equal #
An interface is actually a fat pointer. It stores a pointer to the value, plus information about the type it points to. As it turns out, the information about the type is actually just another pointer. This is where things get interesting. What if one pointer is nil but not the other? As far as I know, it is impossible to construct an interface value where the type pointer is nil but the data pointer isn’t. This leaves two options: either both pointers are nil, or just the data pointer is. These values are not the same.