Why can't I duplicate a slice with `copy()`?
GoSliceGo Problem Overview
I need to make a copy of a slice in Go and reading the docs there is a copy function at my disposal.
> The copy built-in function copies elements from a source slice into a > destination slice. (As a special case, it also will copy bytes from a > string to a slice of bytes.) The source and destination may overlap. > Copy returns the number of elements copied, which will be the minimum > of len(src) and len(dst).
But when I do:
arr := []int{1, 2, 3}
tmp := []int{}
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)
My tmp
is empty as it was before (I even tried to use arr, tmp
):
[]
[1 2 3]
You can check it on go playground. So why can not I copy a slice?
Go Solutions
Solution 1 - Go
The builtin copy(dst, src)
copies min(len(dst), len(src))
elements.
So if your dst
is empty (len(dst) == 0
), nothing will be copied.
Try tmp := make([]int, len(arr))
(Go Playground):
arr := []int{1, 2, 3}
tmp := make([]int, len(arr))
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)
Output (as expected):
[1 2 3]
[1 2 3]
Unfortunately this is not documented in the builtin
package, but it is documented in the Go Language Specification: Appending to and copying slices:
> The number of elements copied is the minimum of len(src)
and len(dst)
.
Edit:
Finally the documentation of copy()
has been updated and it now contains the fact that the minimum length of source and destination will be copied:
> Copy returns the number of elements copied, which will be the minimum of len(src) and len(dst).
Solution 2 - Go
Another simple way to do this is by using append
which will allocate the slice in the process.
arr := []int{1, 2, 3}
tmp := append([]int(nil), arr...) // Notice the ... splat
fmt.Println(tmp)
fmt.Println(arr)
Output (as expected):
[1 2 3]
[1 2 3]
As pointed out in the comments below, append
may allocate excess memory if the slice isn't sized correctly to begin with. A nice solution to this is to preallocate a slice of the right capacity, like so:
tmp := append(make([]int, 0, len(arr)), arr...)
So a shorthand for copying array arr
would be append(make([]int, 0, len(arr)), arr...)
Solution 3 - Go
The copy() runs for the least length of dst and src, so you must initialize the dst to the desired length.
A := []int{1, 2, 3}
B := make([]int, 3)
copy(B, A)
C := make([]int, 2)
copy(C, A)
fmt.Println(A, B, C)
Output:
[1 2 3] [1 2 3] [1 2]
You can initialize and copy all elements in one line using append() to a nil slice.
x := append([]T{}, []...)
Example:
A := []int{1, 2, 3}
B := append([]int{}, A...)
C := append([]int{}, A[:2]...)
fmt.Println(A, B, C)
Output:
[1 2 3] [1 2 3] [1 2]
Comparing with allocation+copy(), for greater than 1,000 elements, use append. Actually bellow 1,000 the difference may be neglected, make it a go for rule of thumb unless you have many slices.
BenchmarkCopy1-4 50000000 27.0 ns/op
BenchmarkCopy10-4 30000000 53.3 ns/op
BenchmarkCopy100-4 10000000 229 ns/op
BenchmarkCopy1000-4 1000000 1942 ns/op
BenchmarkCopy10000-4 100000 18009 ns/op
BenchmarkCopy100000-4 10000 220113 ns/op
BenchmarkCopy1000000-4 1000 2028157 ns/op
BenchmarkCopy10000000-4 100 15323924 ns/op
BenchmarkCopy100000000-4 1 1200488116 ns/op
BenchmarkAppend1-4 50000000 34.2 ns/op
BenchmarkAppend10-4 20000000 60.0 ns/op
BenchmarkAppend100-4 5000000 240 ns/op
BenchmarkAppend1000-4 1000000 1832 ns/op
BenchmarkAppend10000-4 100000 13378 ns/op
BenchmarkAppend100000-4 10000 142397 ns/op
BenchmarkAppend1000000-4 2000 1053891 ns/op
BenchmarkAppend10000000-4 200 9500541 ns/op
BenchmarkAppend100000000-4 20 176361861 ns/op
Solution 4 - Go
If your slices were of the same size, it would work:
arr := []int{1, 2, 3}
tmp := []int{0, 0, 0}
i := copy(tmp, arr)
fmt.Println(i)
fmt.Println(tmp)
fmt.Println(arr)
Would give:
3
[1 2 3]
[1 2 3]
From "Go Slices: usage and internals":
> The copy function supports copying between slices of different lengths (it will copy only up to the smaller number of elements)
The usual example is:
t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t
Solution 5 - Go
The best way to clone as slice is
sClone = append(s[:0:0], s...)
This implementation has two advantage:
-
make sure that the result sClone is nil if s is nil, and is not nil if s is not nil.
-
No need to import the containing package of type T even if T is declared in another package
Solution 6 - Go
> The Go Programming Language Specification > > Appending to and copying slices > > The function copy copies slice elements from a source src to a > destination dst and returns the number of elements copied. Both > arguments must have identical element type T and must be assignable to > a slice of type []T. The number of elements copied is the minimum of > len(src) and len(dst). As a special case, copy also accepts a > destination argument assignable to type []byte with a source argument > of a string type. This form copies the bytes from the string into the > byte slice. > > copy(dst, src []T) int > copy(dst []byte, src string) int
tmp
needs enough room for arr
. For example,
package main
import "fmt"
func main() {
arr := []int{1, 2, 3}
tmp := make([]int, len(arr))
copy(tmp, arr)
fmt.Println(tmp)
fmt.Println(arr)
}
Output:
[1 2 3]
[1 2 3]
Solution 7 - Go
> NOTE: This is an incorrect solution as @benlemasurier proved
Here is a way to copy a slice. I'm a bit late, but there is a simpler, and faster answer than @Dave's. This are the instructions generated from a code like @Dave's. These is the instructions generated by mine. As you can see there are far fewer instructions. What is does is it just does append(slice)
, which copies the slice. This code:
package main
import "fmt"
func main() {
var foo = []int{1, 2, 3, 4, 5}
fmt.Println("foo:", foo)
var bar = append(foo)
fmt.Println("bar:", bar)
bar = append(bar, 6)
fmt.Println("foo after:", foo)
fmt.Println("bar after:", bar)
}
Outputs this:
foo: [1 2 3 4 5]
bar: [1 2 3 4 5]
foo after: [1 2 3 4 5]
bar after: [1 2 3 4 5 6]
Solution 8 - Go
Sweet, Simple, Performant, No need to be careful of length, No Memory overlap, Different copies
slice2 := append([]int{}, slice1...)
Solution 9 - Go
If you don't care about the speed:
import "golang.org/x/exp/slices"
tmp := slices.Clone(arr)
With Go 1.18 and generics, any slices now could be copied with slices.Clone
from package "golang.org/x/exp/slices"
. Playground