Updated on January 30, 2024
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!
Support the Freshman blog
Lots of time and effort goes into creating all the content on this
website. If you enjoy my work, consider supporting what I do through a
financial donation. You can support the Freshman blog with a one-time or
monthly donation through one of the following channels: