How to Deep Copy or Duplicate a Slice in Go

This article provides a comprehensive guide on duplicating slices in Go, highlighting the importance of using copy() and append() for deep copying.

In Go, simply assigning one slice to another doesn’t create a new, independent copy. Instead, both variables refer to the same underlying array, meaning changes in one slice will reflect in the other.

This shared reference becomes apparent in function calls, as slices are passed by reference:

go
func changeSlice(s []int) {
	if len(s) > 0 {
		s[0] = 5
	}
}

func main() {
	s1 := []int{1, 2, 3, 4, 5}
	s2 := s1

	fmt.Println(s1, s2) // [1 2 3 4 5] [1 2 3 4 5]

	s2[1] = 10          // this change is reflected in both s1 and s2
	fmt.Println(s1, s2) // [1 10 3 4 5] [1 10 3 4 5]

	changeSlice(s1)
	fmt.Println(s1, s2) // [5 10 3 4 5] [5 10 3 4 5]
}

1. Deep copying with copy()

The built-in copy() function creates an independent copy of a slice. It requires initializing a new slice of the appropriate length first:

go
func main() {
	s1 := []int{1, 2, 3, 4, 5}
	s2 := make([]int, len(s1))

	_ = copy(s2, s1) // s2 is now an independent copy of s1
	fmt.Println(s1, s2) // [1 2 3 4 5] [1 2 3 4 5]

	s2[1] = 10 // changing s2 does not affect s1
	fmt.Println(s1, s2) // [1 2 3 4 5] [1 10 3 4 5]
}

The copy() function in Go requires two arguments: the destination slice first and the source slice second, with both being of the same type. It returns the count of elements copied. You can disregard this return value if it’s not needed.

A key aspect of copy() is that the lengths of the destination and source slices can differ; it copies elements only up to the length of the shorter slice. Therefore, copying to a slice that is empty or nil results in no elements being transferred.

go
func main() {
	s1 := []int{1, 2, 3, 4, 5} // s1 is length 5
	s2 := make([]int, 3)       // s2 is length 3

	_ = copy(s2, s1)
	fmt.Println(s2) // [1 2 3]

	var s3 []int // nil slice of length 0
	_ = copy(s3, s1)
	fmt.Println(s3) // []
}

An important edge case to note with copy() is that the destination slice remains non-nil, even if the source slice is nil:

go
func main() {
	var s1 []int
	s2 := make([]int, len(s1))

	copy(s2, s1)
	fmt.Println(s1 == nil, s2 == nil) // true false
}

To ensure that the destination slice also becomes nil if the source is nil, you can modify your code as follows:

go
func main() {
	var s1 []int
	s2 := make([]int, len(s1))

	if s1 == nil {
		s2 = nil
	} else {
		copy(s2, s1)
	}

	fmt.Println(s1 == nil, s2 == nil) // true true
}

2. Deep copying with append()

The built-in append() offers another way to duplicate a slice, ensuring the new slice is independent of the original:

go
func main() {
	s1 := []int{1, 2, 3, 4, 5}
	s2 := []int{}
	s2 = append([]int(nil), s1...)

	fmt.Println(s1, s2) // [1 2 3 4 5] [1 2 3 4 5]

	s1[0] = 20
	fmt.Println(s1, s2) // [20 2 3 4 5] [1 2 3 4 5]
}

In this scenario, elements from s1 are appended to a nil slice, and the resultant slice becomes s2. This approach ensures a complete duplication of the slice, irrespective of the destination’s initial length, unlike the copy() function. It’s important to note that the final length of s2 adjusts to match the length of s1, either by truncating or expanding as necessary.

When using append(), if the source slice is non-nil but empty, the resulting destination slice becomes nil:

go
func main() {
	s1 := make([]int, 0)
	var s2 []int
	s2 = append([]int(nil), s1...)

	fmt.Println(s1 == nil, s2 == nil) // false true
}

To address this, create a three-index subslice from the original slice and append it instead. This method ensures the new slice does not share any elements with the source by setting both the length and capacity of the subslice to zero:

go
func main() {
	s1 := make([]int, 0)
	var s2 []int
	s2 = append(s1[:0:0], s1...)

	fmt.Println(s1 == nil, s2 == nil) // false false
}

Final thoughts

In this article, we explored two methods for cloning a slice in Go, while addressing some important edge cases related to either technique. If you have additional insights or methods to share, please feel free to contribute in the comments section.

Thank you for reading, and happy coding!