Page Content

Tutorials

What Are The Pointers In Golang With Clear Code Examples

A basic idea in Go, pointers enable programs to refer to memory regions where values are stored instead of the values themselves. When functions need to alter the original data of a variable that has been supplied to them, this capability is essential.

What are Pointers in Golang?

  • A variable that holds another variable’s memory address is called a pointer. Instead of storing the value itself, it “points” to the location of the item in memory.
  • In computer science, pointers are a type of indirection that can be a very useful tool.
  • While unsafe pointer manipulation was a problem in some previous languages, such as C language, Go offers pointers that prioritise memory safety and steer clear of frequent hazards like “dangling pointers.”

& and * Operators

Go works with pointers using two main operators:

& (Address Operator): A variable’s memory location can be found using the ampersand & operator. As an illustration, &x yields a pointer to the variable x.

* (Dereference Operator): The operator asterisk * has two functions:

  • Declaring a pointer type: In a variable declaration, it signifies that the variable is a pointer to a value of that type (for example, *int). “pointer to an integer” is what *int, for example, signifies.
  • Dereferencing a pointer: The value that the pointer points to can be accessed or changed when it is prefixed to a pointer variable (for example, *xPtr). The statement “store the integer 0 in the memory location xPtr refers to” is an example of what *xPtr = 0.

Example:

package main
import "fmt"
func main() {
    var creature string = "shark" // Declare a string variable
    var pointer *string = &creature // Declare a pointer to a string and assign the address of 'creature' 
    fmt.Println("creature =", creature)
    fmt.Println("pointer =", pointer)   
    fmt.Println("*pointer =", *pointer) // Dereference 'pointer' to get its value. 
    *pointer = "jellyfish" // Modify the value through the pointer
    fmt.Println("*pointer =", *pointer) 
    fmt.Println("creature =", creature) // 'creature' variable's value has also changed. 
}

Output

creature = shark
pointer = 0xc0000140a0
*pointer = shark
*pointer = jellyfish
creature = jellyfish

Pointers as Function Arguments (Call by Reference)

In Go, functions receive a copy of the argument since arguments are transmitted by value. You send a pointer to the original variable to a function so that it can change it. The function can then access and alter the data at that location after receiving a copy of the memory address.

Example:

package main
import "fmt"
func zero(x int) { // This function receives a copy of the integer value 
    x = 0
}
func zeroWithPointer(xPtr *int) { // This function receives a pointer to an integer 
    *xPtr = 0 // Dereference the pointer to modify the original value
}
func main() {
    x := 5
    zero(x) // 'x' is passed by value, its original value won't change 
    fmt.Println("Value of x after zero(x):", x) 
    y := 5
    zeroWithPointer(&y) // Pass the address of 'y' (a pointer) 
    fmt.Println("Value of y after zeroWithPointer(&y):", y) 
}

Output

Value of x after zero(x): 5
Value of y after zeroWithPointer(&y): 0

Built-in new function

Alternatively, you can build a pointer using the built-in new function. Returning a pointer to the freshly created memory, it takes a type as an argument, allocates enough memory for a value of that type, and initialises it to its zero value (e.g., 0 for integers, “” for strings, and nil for pointers). Go is garbage-collected, unlike some other languages, thus you don’t have to use new to manually release memory that has been allocated.

Example:

package main
import "fmt"
func main() {
    xPtr := new(int) // Allocates memory for an int and returns a pointer to it (initialized to 0) 
    fmt.Println(*xPtr) 
    *xPtr = 100 // Assign a value through the pointer 
    fmt.Println(*xPtr) 
}

Output

0
100

Pointers with Structs

Using pointers in conjunction with structs is quite beneficial. When working with pointers to structs, Go offers ergonomic facilities that allow you to access the fields of the pointer without manually dereference. Direct use of the. operator on the pointer is possible.

Example:

package main
import "fmt"
type Circle struct {
    x, y, r float64
}
func main() {
    // Creating a struct instance and getting a pointer to it using & 
    c := &Circle{0, 0, 5}
    fmt.Println(c)
    // Accessing fields using the . operator on the pointer (Go automatically dereferences) 
    fmt.Println(c.x, c.y, c.r) 
    c.x = 10                  // Modifying a field through the pointer 
    fmt.Println(c.x)          
}

Output

&{0 0 5}
0 0 5
10

Pointers with Arrays

A pointer to an array can be created by prefixing composite literals for arrays with the & operator, just like with structs. Additionally, arrays offer automatic dereferencing whether they are indexed or sliced, so when superpowers is a pointer to an array, it is equal to (*superpowers). This makes it possible for functions to change array elements by getting a pointer.

Pointers with Slices and Maps (Under the Hood)

Although explicit pointers to slices and maps can be declared, this is frequently not required because slices automatically point to underlying arrays and maps are actually pointers disguised as slices.

Maps: When maps are assigned or supplied as arguments, they act like pointers and are not copied. This makes it unnecessary to take a pointer to a map explicitly (*map[string]string).

Slices: The internal representation of a slice is a structure that holds the length, capacity, and pointer to an array. The array’s underlying contents can be changed when a slice is sent straight to a function or method with this internal pointer. In general, an explicit pointer to a slice (*[]string) is only helpful if you want to change the slice’s header (length, capacity, or starting offset) instead of its contents.

Nil Pointers

The value nil is assigned to a pointer that has nowhere to point. The zero value for interfaces, maps, slices, and pointers is nil. You will experience a runtime panic if you try to dereference a nil pointer, such as by executing methods or accessing fields on a nil pointer. It’s important to make sure a pointer is nil before doing anything with it in order to avoid this.

Example of nil pointer panic and how to guard against it:

package main
import "fmt"
type Creature struct {
    Species string
}
func changeCreature(creature *Creature) {
    // Check for nil before dereferencing to prevent panic
    if creature == nil {
        fmt.Println("Cannot change a nil creature.")
        return
    }
    creature.Species = "jellyfish"
    fmt.Printf("Changed creature: %+v\n", creature)
}
func main() {
    var creaturePtr *Creature // Declared but not initialized, so it's nil
    fmt.Printf("Initial: %+v\n", creaturePtr)
    // This call would panic without the nil check in changeCreature
    changeCreature(creaturePtr) 
}

Output

Initial: <nil>
Cannot change a nil creature.

Method Pointer Receivers

The type on which a method operates is specified by the receiver argument when the method is defined.

  • If a method has a value receiver (e.g., func (c Creature) Greet()), it operates on a copy of the value, and any changes made within the method will not affect the original instance.
  • If a method has a pointer receiver (e.g., func (c *Creature) Greet()), it receives a pointer to the instance. This allows the method to mutate (change) the original instance’s attributes. Go automatically determines the address of a variable when calling methods with dot notation, so nathan.birthday() will work even if birthday has a pointer receiver.

Example:

package main
import "fmt"
type Person struct {
    Name string
    Age  int
}
// Method with a pointer receiver. It can modify the original Person instance. 
func (p *Person) birthday() {
    p.Age++ // Modifies the Age field of the original Person
}
// Method with a value receiver. It operates on a copy, original won't change. 
func (p Person) celebrate() {
    fmt.Printf("%s is now %d years old (copy).\n", p.Name, p.Age+1)
}
func main() {
    terry := &Person{Name: "Terry", Age: 15} // Get a pointer to a Person
    terry.birthday() // Call method using the pointer receiver
    fmt.Printf("Terry after birthday (pointer): %+v\n", terry) 
    nathan := Person{Name: "Nathan", Age: 17} // A value type 
    nathan.birthday() // Go automatically takes the address for the method call if receiver is pointer
    fmt.Printf("Nathan after birthday (value): %+v\n", nathan) 
    sammy := Person{Name: "Sammy", Age: 10}
    sammy.celebrate() // Call method with value receiver
    fmt.Printf("Sammy after celebrate (original): %+v\n", sammy) 
}

Output

Terry after birthday (pointer): &{Name:Terry Age:16}
Nathan after birthday (value): {Name:Nathan Age:18}
Sammy is now 11 years old (copy).
Sammy after celebrate (original): {Name:Sammy Age:10}

If a method provides a pointer receiver, an interface expecting that method will only accept the pointer version of the type.

General Advice on Pointers

  • Don’t overuse points, but use them when appropriate. Code that stays away from pointers can be easier to read.
  • Think in Go terms: While shared memory may be prevalent in other languages, Go frequently offers goroutines faster, safer, and more effective means of communication, such as channels.
  • A pointer as a parameter or a pointer receiver for a method are usually needed if a function needs to change the data it receives, particularly structs and arrays.
Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index