Pages

Monday, January 15, 2018

Golang for dummies - Pointers

If you're a code newbie, then perhaps you have never had the privilege of attending traditional programming classes where you learn about the Voldemort of programming languages, C.

I haven't.


With a nontraditional entry to tech, I learned how to program in kind, interpreted languages such as Python and Ruby. I see a fair amount of C around my job, but I've never formally learned it.


Let me get to why prior knowledge of C is important when trying to understand Golang.


From my perspective, Golang is frustrating and tricky and full of concepts I do not fully understand. And every time I look up anything Go related, I seem to find myself in an article or blog post that says "Golang is beautiful, elegant, and powerful! It is just like C without any of the annoying stuff! It has garbage collection!" There's some serious swooning going on. So it seems that if you're coming at Go from C, people have this blissful experience. But, if you're coming at it from Python, you wonder, "What are structs? Why are there ampersands all over this code? And why is everyone so excited about this thing called concurrency?" And unfortunately, most of the "learn how to Go" books and websites out there assume you know C.


I've decided to take some time learning and sharing about concepts in Go that confuse me. This post's topic is about pointers. Let's begin.


Pointers in Golang


What is a pointer in general?


Ever tried to print out a variable and gotten a weird number like 0x12345678 ?
This is what is known as a pointer. Another way to think of pointers is as a memory address. (Literally, those two are the same thing. Just think of whichever work helps you understand best.) All of memory is basically a giant array, where pointers are the indices of said array. A pointer/memory address can, just like anything else, be assigned to a variable. A pretty popular comparison is that of page numbers a book and the actual content of those pages. The page numbers are the pointers to the information that is printed on the page. So, rather than telling someone the entire recipe for chocolate cake, you hand them your cook book and say, "Chocolate cake is on page 174". You can see how that is more efficient and saves everyone a lot of time, and it is just the same for pointers to chunks of memory.


How do pointers work in Go?


Each programming language implements pointers differently. In Go, pointers are a type, more specifically of type <typeof address content>. Specifically we say “a pointer that points at an address that contains an integer is of type int pointer.
The & operator is used to obtain the address of a variable.

x := 37
&x
← address where x is stored.

The * character is used on a pointer to access the content of the address a pointer points to. This is called dereferencing. More on this below.

The * is also used to define a pointer, e.g:
var x *int
Let me walk you through some examples I wrote in the rather excellent Go Playground.
If you carefully look at the output of this program, you will be able to see how it works in practice. We declare a variable, x, and set it to 37. We declare that PointerToX is a pointer of Type int. When we print both variables, fmt prints 37 and <nil>, since we only declared a pointer, we didn't assign anything to it. In Line 16, we obtain the address at which x is stored, and assign it to PointerToX. Now, when we print PointerToX, we get a memory address. To double check, printing *PointerToX dereferences the pointer and gives the content of said address, namely 37.
The next bit is the truly fun stuff. We can put a different int in the place that PointerToX refers to, and it will change the value of x without doing anything to x itself on the face of it. With a pointer, we can access x's space in memory and alter its content, which means x now has the altered value (in this case, 42.)





Another cool thing to explore is something I took from the Golang Book chapter on pointersIn both programs, we're trying to set x to zero after having declared it to be 5. But in the first case, the zero function merely passes a copy of x, sets that to 5, and exits without returning anything. In the second case, the function takes an *int (pointer) that points at x, and zero changes the content of the address. It also returns nothing, but the changed content remains.




































Golang supports pointer types. Each pointer refers to the type of data that it points at. There are int pointers, string pointers, struct pointers, all depending on what you're pointing at. You cannot change the underlying data type of a pointer - in the above example, you can set *xPtr to 0, but you cannot set it to "hello".



The builtin new function is how you can get new pointers. Golang is nice and assigns default memory content to a newly declared pointer - 0 to *int, the empty string "" to *string, and so on. Try it out! The reason this is nice is, well, C isn't nice about this. More on that below.






One thing that confused the heck out of me when first coding in Golang was that there were stars and ampersands everywhere, pointers being passed to functions, seemingly without rhyme or reason, especially when dealing with more complex types, i.e. structs. The thing to remember is that the new function returns a pointer, always. The new function is also a preferred way to create a new instance of a struct. What is really confusing is that the dot operator (.) lets you access the properties of a struct the exact same way as the properties of a *struct. But if you try to pass a struct to a function that only takes *structs, that is, pointers to your struct, you'll get in trouble.




Why use pointers?

Because pointers reference a location in memory, they can be faster and lighter than copying the actual content from memory. This can be especially useful for large structs.
As well, as you have seen, with pointers you can access the underlying memory and alter it directly.




Pointer dangers

When a pointer points at a memory address that doesn’t exist, it is a wild pointer and it can result in a segmentation fault or segfault.
Most other pointer dangers are handled by the Golang language, as you will see by comparing it with C.
In C, it is possible to have a dangling pointer, which means that the content of the address has been deallocated but the pointer still expects the former content. In Go, automatic garbage collection removes this danger. Other languages implement “smart” or “safe” pointers.
C supports pointer arithmetic. Basically, this means it is possible to access the next place in memory by adding +1 to the pointer.
This is very dangerous, as it can grant access to memory not meant to be accessed.

Golang does not permit pointer arithmetic.

A note on references

Reference is a word often used interchangeably with pointer. Mostly, this is true - a pointer "refers" to a place in memory; it is therefore a reference. However, some languages, like C++, have separate reference types that function similarly to pointers yet are not pointers (C++ also has pointer types, just to confuse you). In general, however, "pointers and references" are broadly the same thing. Just as with anything, each language has its own flavor on it.

2 comments: