Golang trap: how to copy structs properly
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 😉