Golang Gotcha: Modifying an array of structs

Scenario

Using Golang, we initialize an array of structs, within which we’d like to change a field by iterating over the initialized array. This would be necessary (for example) to initialize certain struct fields.

An initial pass at the code may look something like this:

// play with this code here: https://go.dev/play/p/VH0duYxZVZn
package main

import "fmt"

type DemoType struct {
	Test string
}

var DemoTypeArray = []DemoType{
	DemoType{
		Test: "asdfg",
	},
	DemoType{
		Test: "qwerty",
	},
}

func main() {

	fmt.Printf("==================================\nCycling and 'modifying':\n==================================\n")

	for _, demoTypeInstance := range DemoTypeArray {
		fmt.Println(demoTypeInstance.Test)
		demoTypeInstance.Test = "modified"
	}

	fmt.Printf("==================================\nAfter 'modification':\n==================================\n")

	for _, demoTypeInstance := range DemoTypeArray {
		fmt.Println(demoTypeInstance.Test)
	}
}

The output of the above code playground ( https://go.dev/play/p/VH0duYxZVZn ) would show:

==================================
Cycling and 'modifying':
==================================
asdfg
qwerty
==================================
After 'modification':
==================================
asdfg
qwerty

The below line should modify the “Test” field of each struct to “modified”:

demoTypeInstance.Test = "modified"

But clearly that didn’t happen. What gives? In more complex programs this could end up leading to a panic due to a nil reference. A careful inspection of the code reveals the culprit is the for loop. The “range” command outputs two variables… the index of a given item in an array and a copy of the item in the array.

That last comment is key. In our for loop, we are actually modifying the copy of the data, not the data item in the array itself. A solution is to use the index to modify the actual data item, as follows:

// play with this code here: https://go.dev/play/p/NA089ZOURdr
package main

import "fmt"

type DemoType struct {
	Test string
}

var DemoTypeArray = []DemoType{
	DemoType{
		Test: "asdfg",
	},
	DemoType{
		Test: "qwerty",
	},
}

func main() {

	fmt.Printf("==================================\nCycling and 'modifying':\n==================================\n")

	for idx, demoTypeInstance := range DemoTypeArray {
		fmt.Println(demoTypeInstance.Test)
		DemoTypeArray[idx].Test = "modified"
	}

	fmt.Printf("==================================\nAfter 'modification':\n==================================\n")

	for _, demoTypeInstance := range DemoTypeArray {
		fmt.Println(demoTypeInstance.Test)
	}
}

The output of the above code playground ( https://go.dev/play/p/NA089ZOURdr ) would show:

==================================
Cycling and 'modifying':
==================================
asdfg
qwerty
==================================
After 'modification':
==================================
modified
modified

Success! Note the following in the modified code:

  • In our for loop, we no longer ignore the index argument, we now store it in “idx”
  • To modify the item stored in the array we user the idx variable to directly reference the item within the array