How to write a Golang Deep Copy method

6 min read

How to Golang Deep Copy a complex data structure

Deep copying is a fundamental concept in programming that allows developers to create an independent and complete copy of a data structure or object. In Golang, deep copying plays a crucial role in preserving the integrity of data during complex operations or when working with pointers. In this blog post, we will explore the concept of Golang deep copying, its significance, and the various techniques you can use to achieve it effectively.

Deep copying refers to creating a duplicate of a data structure or object where each element within the structure is copied, recursively, to create independent instances. Unlike shallow copying, which only duplicates the references to objects, deep copying ensures that any changes made to the original structure do not affect the copied structure.

The Importance of Deep Copying in Golang

Deep copying is particularly crucial in scenarios where multiple goroutines or concurrent processes are involved, as it helps maintain data integrity. By creating independent copies, each goroutine or process can manipulate its own version of the data structure without interfering with others.

Golang deep copying is valuable when working with complex data structures, such as nested maps, slices, or structs. These structures often contain references to other objects, and deep copying ensures that all nested objects are also duplicated properly.

Want to be the first to hear about Go jobs?

Sign up to the Work in Golang weekly digest:

Techniques for Deep Copying in Golang

Manual Copying

One way to achieve deep copying in Golang is by manually copying each element of the data structure. This approach requires understanding the structure's composition and creating new instances of each element, recursively, until the entire structure is duplicated. While manual copying offers fine-grained control, it can be tedious and error-prone for complex structures.

Using External Libraries

Libraries such as github.com/mohae/deepcopy or github.com/ulule/deepcopier offer convenient copying utilities in Golang that make use of the reflect package. These libraries often provide high-performance and easy-to-use functions for deep copying various data structures.

Writing your own golang deep copy method

We can make use of the in-built Golang JSON Marshal and Unmarshal to perform a deep copy operation. This won't necessarily be the most efficient operation, however it is simple to implement and results in the desired outcome.

package main import ( "encoding/json" "fmt" "log" ) type nestedObject struct { Value int } type myComplexObject struct { Text string NestedObject *nestedObject } func DeepCopy(original any, copy any) error { b, err := json.Marshal(original) if err != nil { return err } err = json.Unmarshal(b, copy) if err != nil { return err } return nil } func main() { obj := &myComplexObject{ Text: "Hello world", NestedObject: &nestedObject{ Value: 1, }, } cpy := &myComplexObject{} err := DeepCopy(obj, cpy) if err != nil { log.Println(err) } obj.Text = "update original" obj.NestedObject.Value = 9999 fmt.Println("Original object ptr:", &obj, &obj.NestedObject, "Copy ptr:", &cpy, &cpy.NestedObject) fmt.Println("Original object values:", *obj, *obj.NestedObject, "Copy values:", *cpy, *cpy.NestedObject) } // Output: Original object ptr: 0xc00011e018 0xc000116028 Copy ptr: 0xc00011e020 0xc000116040 Original object values: {update original 0xc000126008} {9999} Copy values: {Hello world 0xc000126160} {1}

This code demonstrates a Golang deep copy operation on a complex object in Go. First the code defines two struct types: nestedObject and myComplexObject. nestedObject has a single field Value of type int where as myComplexObject has two fields: Text, which is a string, and NestedObject, which is a pointer to a nestedObject.

We then create a function called DeepCopy, which performs a deep copy of an object using JSON marshaling and unmarshaling. The function takes two arguments: original and copy, both of type any (which means they can be of any type). The function returns an error if there is an issue with marshaling or unmarshaling.

The expected output shows that the original and copied objects have different memory addresses, indicating that the deep copy was successful. Additionally, the original object's values have been modified while the copied object's values remain unchanged, further confirming its success.

Why would I want to deep copy?

When using pointers you'll want to make sure that the variable that is passed in to a for loop is not being mutated between iterations, thus resulting in a differing state between ietrations. To achieve this, we can either dereference the pointer and then assign it to a new pointer, thus creating a high level new copy of the struct, or you can use a deep copy method to create an entirely new copy of a complex data structure. Depending on your needs the shallow copy might be more desirable, however as I was chasing a bug as a result of mutating states, a deep copy was the appropriate solution.

Other reasons for deep copying include:

  • Data Integrity and ensuring that each copy of the data structure is completely independent.
  • In some cases, you may want to create immutable copies of data structures. Deep copying allows you to create new instances that cannot be modified, providing enhanced control and preventing unintended changes.
  • When working with complex data structures that contain references to other objects, shallow copying (which copies the references) can lead to unintended side effects. Deep copying creates completely independent copies, preventing any unintended modifications or interactions between the original and copied structures.
  • Deep copying is often used for creating snapshots or checkpoints of data at a specific point in time. This is useful in scenarios where you need to preserve the state of the data structure for later analysis or restoration.
  • Deep copying is commonly employed when implementing object cloning in object-oriented programming. By creating deep copies of objects, you can create new instances with the same properties and behaviors as the original object, allowing for independent manipulation.

When considering whether to use this technique you should consider whether you need independent, isolated copies of data structures and to prevent unintended side effects, or whether you just need a quick high-level copy of your object. As always, there are trade-offs, and it is for you to decide how to best approach the problem at hand.