- read

Golang trap: how to copy structs properly

Wacław The Developer 31

Instead of introduction

Take a look at that code carefully:

package main

import "fmt"

type
car struct {
model string
owners []string
}

func main() {
c := car{
model: "BMW",
owners: []string{"John", "Martha"},
}
cCopy := c

fmt.Printf("%+v\n", cCopy)
c.owners[0] = "Antony" //Change the original c.owners
c.model = "Audi" //Change the original c.model
fmt.Printf("%+v", cCopy) //Print the copied struct again
}

Try to run that. The output will be:

{model:BMW owners:[John Martha]}
{model:BMW owners:[Antony Martha]}

Why the changing of the original struct affects the copied struct?

The short answer: because c.owners is a reference to data, not the data itself. So, the s.model will be copied by cCopy := c, but c.owners not. In the same way, the pointers will not be copied too.

The correct solution to copy such struct:

package main

import "fmt"

type
car struct {
model string
owners []string
}

func main() {
c := car{
model: "BMW",
owners: []string{"John", "Martha"},
}

cCopy := car{model: c.model}
for _, owner := range c.owners { //Copying by loop
cCopy.owners = append(cCopy.owners, owner)
}

fmt.Printf("%+v\n", cCopy)
c.owners[0] = "Antony"
c.model = "Audi"
fmt.Printf("%+v", cCopy)
}

To make your code nicer, replace the loop

for _, owner := range c.owners { //Copying by loop
cCopy.owners = append(cCopy.owners, owner)
}

with

cCopy.owners = append(cCopy.owners, c.owners...)

Finally, the output will be:

{model:BMW owners:[John Martha]}
{model:BMW owners:[John Martha]}

If your struct contains the pointer

I changed the example to show what we need to do with pointers in the structure. Let’s run this example

package main

import "fmt"

type
car struct {
model string
owner *string
}

func main() {
c := car{
model: "BMW",
owner: getStrPtr("John"),
}

cCopy := c

fmt.Printf("%s owner's name: %s\n", cCopy.model, *cCopy.owner)
*c.owner = "Antony"
c.model = "Audi"
fmt.Printf("%s owner's name: %s", cCopy.model, *cCopy.owner)
}

func getStrPtr(s string) *string {
return &s
}

As you can see from the output — the changed value of the original pointer also changed in cCopy.owner, because c.owner contains just the address in memory, not the actual data:

BMW owner's name: John
BMW owner's name: Antony

How to copy the pointers:

The easiest way to copy the pointer is to copy the data of the pointer to the destination pointer

package main

import "fmt"

type
car struct {
model string
owner *string
}

func main() {
c := car{
model: "BMW",
owner: getStrPtr("John"),
}

cCopy := c
cCopy.owner = new(string) //Create new pointer replacing original
*cCopy.owner = *c.owner //Copying by accessing to data

fmt.Printf("%s owner's name: %s\n", cCopy.model, *cCopy.owner)
*c.owner = "Antony"
c.model = "Audi"
fmt.Printf("%s owner's name: %s", cCopy.model, *cCopy.owner)
}

func getStrPtr(s string) *string {
return &s
}

The output will be:

BMW owner's name: John
BMW owner's name: John

Conclusion

As you can see — you need to control every data structure, which you are working with. This simple example can show, how important to understand what slice and pointer really are. Also, keep in mind, that nested structs with pointers exist. For example:

type car struct {
model string
owner struct{
name string
licenceID *string
}
}

So, you need to control this and the same situations by copying the data, not just addresses in memory 😉