Shallow copying interface values in Go

12 March 2016

In go shallow copying of struct values can be considered as a feature built in the language. It is pretty obvious from language specification, that whenever you assign struct to a new variable or pass it to a function by value it gets copied.

So in simplest case you have to dereference pointer to a struct type and assign it to a local variable.

Here is an example of how to achieve this.

package main

import "testing"

type simple struct {
	fld string
}

func (s *simple) copy() *simple {
	clone := *s   // This is where we essentially make a new struct
	return &clone // Return reference to a new struct
}

func TestCopy(t *testing.T) {
	original := &simple{"original"}
	copy := original.copy()
	copy.fld = "copy"

	if original.fld != "original" {
		t.Error("Original struct should retain it's state")
	}
	if copy.fld != "copy" {
		t.Error("Copy struct should have new state")
	}
}

But what if the actual type of the value that has to be cloned is not known, because it is passed as an interface type?

Since interface polymorphism does not discern value types and pointers it is impossible to simply dereference interface type, so the following code would not even compile:

func copy(i interface{}) interface{} {
    clone := *s   // interface{} is not a pointer type, so it can't be dereferenced
    return &clone
}

This is when reflection in go comes handy!

Instead of trying directly trying to dereference interface value, we can use reflect.Indirect function, which will return dereferenced value in case argument is a pointer or argument as is if it is a value.

package main

import (
	"testing"
	"reflect"
)

type simple struct {
	fld string
}

func clone(i interface{}) interface{} {
	// Wrap argument to reflect.Value, dereference it and return back as interface{}
	return reflect.Indirect(reflect.ValueOf(i)).Interface()
}

func TestCopy(t *testing.T) {
	original := &simple{"original"}

	copy := clone(original).(simple)
	copy.fld = "copy"

	if original.fld != "original" {
		t.Error("Original struct should retain it's state")
	}
	if copy.fld != "copy" {
		t.Error("Copy struct should have new state")
	}
}

In this example clone function will work with both pointers and values and return interface type, which essentially is a pointer to whatever is actually returned.