Published on

Understanding Slice Modification in Go: Pointers vs. append()

Authors

Pointers

When you pass a variable to a function, it is always passed by value, meaning a copy is made. Therefore, any changes made to the variable within the function will not affect the original variable. To modify the original variable in Go, you need to pass a pointer to it.

Pointers are variables that store the memory address of another variable. They enable you to reference and manipulate the value stored at that address. This allows for more efficient memory usage and the ability to modify the values of variables and data structures that are located elsewhere in memory, typically in another scope or function.

consider the following example:

package main

import "fmt"

func main() {
	a := "string"
	testPointer(a)
	fmt.Printf("a: %s \n", a)
}
func testPointer(a string) {
	a = "another string"
}

If you run the above code, it will return a: string because the variable cannot be changed. However, if you want to modify the variable, you need to pass a as a pointer in the new function by adding the * symbol before the variable type and name in the function declaration. Additionally, you need to also use the & symbol to pass the address of the variable, not just the variable itself.

package main

import "fmt"

func main() {
	a := "string"
	testPointer(&a)
	fmt.Printf("a: %s \n", a)
}
func testPointer(a *string) {
	*a = "another string"
}

The output of the above code would be:

a: another string

So if you pass a variable to a function in Go and you don’t need to change it, you can just pass it by value, which creates a copy of the original variable.

Slices, Maps, and Channels in Go somewhat behave like pointers by default.

Slices, Pointers and The append() Function

Slices in Go are essentially references to arrays with additional metadata like length and capacity. When you pass a slice to a function, you are passing a copy of this reference, not a copy of the entire array.

The append() function adds elements to a slice and returns a new slice. This new slice may have a different underlying array if the existing array does not have sufficient capacity to accommodate the new elements.

Not an Alternative to Pointers

Using append() is not an alternative to using pointers; rather, it is a convenient way to work with slices, which are reference types. Essentially,

  • If you need to modify the contents of a slice within a function, you can use append() directly because the slice reference is passed by value.
  • If you need to modify the original slice itself (e.g., you need to ensure the changes reflect outside the function), you should either return the modified slice or use a pointer to the slice.

Here’s an example demonstrating both approaches:

Without Pointers (Returning the Modified Slice):


package main

import "fmt"

// Function to append an element to a slice and return it
func addElement(slice []int, element int) []int {
    slice = append(slice, element)
    return slice
}

func main() {
    numbers := []int{1, 2, 3}
    fmt.Println("Before function call:", numbers)

    numbers = addElement(numbers, 4) // Return the modified slice and reassign

    fmt.Println("After function call:", numbers)
}

The output of the above code would be:

Before function call: [1 2 3]
After function call: [1 2 3 4]

Using Pointers:

package main

import "fmt"

// Function to append an element to a slice using a pointer
func addElement(slice *[]int, element int) {
    *slice = append(*slice, element)
}

func main() {
    numbers := []int{1, 2, 3}
    fmt.Println("Before function call:", numbers)

    addElement(&numbers, 4) // Pass a pointer to the slice

    fmt.Println("After function call:", numbers)
}

The output of the above code would also be:

Before function call: [1 2 3]
After function call: [1 2 3 4]

Things To Keep In Mind

  • While slices are indeed passed by value, this value is a reference to the underlying array. This means that modifications to the slice's contents within a function will be visible outside the function, even without using pointers.
  • Using pointers to slices is mainly necessary when you need to modify the slice header itself (length, capacity, or the pointer to the underlying array) rather than just its contents.
  • For small slices, passing by value can sometimes be more efficient than using pointers, as it avoids an extra layer of indirection.

In conclusion, the append() function is a powerful tool for working with slices, but it does not replace the concept of pointers. It leverages the reference nature of slices to manage memory efficiently while allowing flexible and dynamic modification of slice contents.