Pointers

Advanced Datatypes

Pointers

🙋 Need help? Ask an expert now!

Cool there are pointers in Go!

Idiomatic Go code -- such as the standard library (or even the Go runtime itself!) -- is full of use cases for pointers! Whenever we want to pass something by reference -- and avoid creating a copy of it -- we send pointers. Go also abstracts away all of the deep details of the pointer implementation, so developers can focus on leveraging the higher-level features. Remember that the Go runtime's garbage collector will manage the references and reclaim memory automatically.

Basic Pointers Example

package main
            
            import (
                "fmt"
            )
            
            type FooStruct struct {
                Children []*FooChild 
            }
            
            type FooChild struct {
                SampleVal int
            }
            
            func main() {
                ptrToFoo := giveMePointer()
                // Expect something like: &{[0x416020 0x416024 0x416028 0x41602c 0x416030 0x416034 0x416038 0x41603c 0x416040 0x416044]}
                fmt.Println(ptrToFoo)
                // Expect something like: Children: [0x416020 0x416024 0x416028 0x41602c 0x416030 0x416034 0x416038 0x41603c 0x416040 0x416044]
                fmt.Println("Children:", ptrToFoo.Children)
            
                for _, child := range ptrToFoo.Children {
                    // Child (ptr): &{42} Dereferenced: {42}
                    fmt.Println("Child (ptr):", child, "Dereferenced:", *child) // The * followed by a pointer, 'dereferences' a pointer and yields the value directly
                }
            
                // When you're playing with pointers, watch out for nil-pointer panics
                var thisIsNil *FooStruct
                // 💣  Boom! Nil-pointer panic and it's game over (this might be a good time to review panic/recover!)
                fmt.Println(thisIsNil.Children)
            }
            
            func giveMePointer() *FooStruct {
                // Initialize a FooStruct with an empty slice for children
                foo := FooStruct{
                    // NB: The []*FooChild syntax means that we're going to have the slice be of pointers to FooChild's -- not directly FooChild's.
                    //     Careful readers will note that this means we are *only* allocating the memory required to store 10 pointers, not 10 FooChild's
                    Children: make([]*FooChild, 10),
                }
                // Populate the foo.Children slice with pointers to FooChild's
                for i := range foo.Children {
                    // The &FooChild syntax initializes the object and gives us the pointer -- as opposed to the struct directly (short-hand notation)
                    foo.Children[i] = &FooChild{
                        SampleVal: 42+i,
                    }
                }
                // Here, we use the &foo to return the pointer to foo, instead of a copy of foo
                return &foo
            }

What if I want to go crazy with pointers?

Go's unsafe packages offers 'unsafe' utilities for working with pointers. The warning is the name of the package. Have fun!

Keep it Idiomatic!

  • Use pointers liberally, they are frequently the most idiomatic way to pass data around

  • Generally avoid pointers to slices, maps, or channels -- that's redundant (since those are already reference-based) and actually might make things harder, for instance, you can't use range with a pointer to a slice.

Edit Me on GitHub!