Unexpected behaviour appending to multidimensional slices

Hi,

I discovered a strange behavior when appending to slices:

Why does

package main

import (
	"fmt"
)

var (
	a  []int
	aa [][]int
)

func main() {
	a = make([]int, 4, 4)

	a[0] = 1
	a[1] = 2
	a[2] = 3
	a[3] = 4

	for i := 0; i < 4; i++ {
		a[0] = a[0] * 2
		a[1] = a[1] * 3
		a[2] = a[2] * 4
		a[3] = a[3] * 5

		aa = append(aa, []int{a[0], a[1], a[2], a[3]}) //[[2 6 12 20] [4 18 48 100] [8 54 192 500] [16 162 768 2500]]
		//aa = append(aa, a) //[[16 162 768 2500] [16 162 768 2500] [16 162 768 2500] [16 162 768 2500]]
		//aa = append(aa, a[:]) //[[16 162 768 2500] [16 162 768 2500] [16 162 768 2500] [16 162 768 2500]]
	}

	fmt.Println(aa)
}

result in

[[2 6 12 20] [4 18 48 100] [8 54 192 500] [16 162 768 2500]]

however, appending the complete slice instead

…
aa = append(aa, a)
…

results in

[[16 162 768 2500] [16 162 768 2500] [16 162 768 2500] [16 162 768 2500]]

?

Or even copying the slice by

…
aa = append(aa, a[:])
…

leads to this annoying behavior,

If that is not a bug, it’s at least a very bad feature!

:thinking:

yes you end up with each element of aa pointing to the same single instance of a
so if you double a[0], then you have doubled aa[0][0], aa[1][0], aa[2][0] and aa[3][0]
as they all point to the same memory location. That is how slices work.

Have a look at Go Slices: usage and internals - The Go Programming Language

1 Like

Thank you for the good explanation!

In that case this is a less quick and more dirty solution:

…
var b []int
b = make([]int, 4, 4)
for i := 0; i < len(a); i++ {
	b[i] = a[i]
}

aa = append(aa, b)
…

But it’s not intuitive and definitely a source of really hard-to-find bugs.

Try something like Go Playground - The Go Programming Language

I had tried

copy(b,a)

as well, however I made the mistake to allocate the memory only once and reassigned values inside loop. And this did not work:

for … {
	…
	a[0] = a[0] * 2
	…
	b := make([]int, 4, 4)
	copy(b, a)
	aa = append(aa, b)
	…
	}

Your approach succeeds, but variable b must be reallocated each time.

b := make([]int, 4, 4)
for … {
	…
	a[0] = a[0] * 2
	…
	copy(b, a)
	aa = append(aa, b)
	…
	}

If you don’t create a new slice with make, then you are back to sharing the slice over and over.