Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deep copying data structures in golang

I want to duplicate an instance of a data structure. Since go does not have any builtins, I am using a third party library: https://github.com/emirpasic/gods.

For example, I may try use deep copy with a hash set.

var c, d hashset.Set
c = *hashset.New()
c.Add(1)
deepcopy.Copy(d, c)
c.Add(2)
fmt.Println(c.Contains(2))
fmt.Println(d.Contains(2))
fmt.Println(c.Contains(1))
fmt.Println(d.Contains(1))

However, the content of the hash set is not copied at all. I know deep copy modules can't copy unexported values, but since there is no builtin "copy constructor" in the library, does it mean it is not possible to fully duplicate a data structure instance with the library without modifying its code? (Similar problem happens with some other libraries I looked into).

I am new to golang and does not feel right, since similar things can be easily achieved for example in C++. I know I could write my own version or modify their code, but it is too much work than expected and that's why I think there should be an idiomatic way.

PS: For people who may say "there is no need of such functionality", I am distributing some complex state with some data structures to parallel calculation threads, they use the states directly and must not interfere with each other.

like image 802
Mayoi Avatar asked May 29 '19 07:05

Mayoi


1 Answers

Unfortunately or not, there is no way to do this in Go. First tool that comes to mind is reflection (package reflect), but using reflection you can only read unexported fields, but you can't set them. See How to clone a structure with unexported field?

The only way to clone structures with unexported fields would be to use package unsafe (see an example here: Access unexported fields in golang/reflect?), but as its name says: it's unsafe and you should stay away from it as much as possible. Programs created using unsafe has no guarantee that they continue to work with newer Go releases, or that they behave the same on every platform.

In general the only and proper way to support cloning in Go is if the package itself supports such operations.

Note #1:

This does not mean that in some specific case you can't "mimic" cloning by creating a new value and building its state manually. For example you can clone a map by creating a new map, iterating over the key-value pairs of the original and setting them in the new map.

Note #2:

Do note that you can make "exact" copies of structs having unexported fields by simply assigning them to another struct variable (of the same type), which will properly copy the unexported fields too.

Like in this example:

type person struct {
    Name string
    age  *int
}

age := 22
p := &person{"Bob", &age}
fmt.Println(p)

p2 := new(person)
*p2 = *p
fmt.Println(p2)

Which will output (try it on the Go Playground):

&{Bob 0x414020}
&{Bob 0x414020}

Which we can even generalize using reflect without relying on the concrete type:

type person struct {
    Name string
    age  *int
}

age := 22
p := &person{"Bob", &age}
fmt.Println(p)

v := reflect.ValueOf(p).Elem()
vp2 := reflect.New(v.Type())
vp2.Elem().Set(v)
fmt.Println(vp2)

Try this one on the Go Playground.

But what we can't do is change the person.age unexported field to point to something else. Without help of the declaring package, it can only be nil or the same pointer value (pointing to the object as the original field).

Also see related: Quicker way to deepcopy objects in golang

like image 181
icza Avatar answered Sep 28 '22 08:09

icza