Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could anyone explain this strange behaviour of appending to golang slices

Tags:

arrays

slice

go

The program below has unexpected output.

func main(){
    s:=[]int{5}
    s=append(s,7)
    s=append(s,9)
    x:=append(s,11)
    y:=append(s,12)
    fmt.Println(s,x,y)
}

output: [5 7 9] [5 7 9 12] [5 7 9 12]

Why is the last element of x 12?

like image 326
user4378227 Avatar asked Dec 19 '14 14:12

user4378227


People also ask

Does Golang append create a new slice?

If the length crosses the capacity, it will panic at run time. The capacity is a limitation in reslicing. So, golang provides an built-in function append() to fix this issue. append() appends zero or more values to a slice and returns the resulting slice.

How slice append works in Golang?

The built-in append function appends elements to the end of a slice: if there is enough capacity, the underlying array is reused; if not, a new underlying array is allocated and the data is copied over.

Is Golang slice inclusive?

operator, Go calculates the size of the array for us. We create a slice from the vals array. The resulting slice contains elements starting from index 5 up to index 7; the upper bound is non-inclusive.

What is the main difference between array and slice in Golang?

Since a slice doesn't have a specific size, we can append values to a slice in addition to changing its existing values. But in Arrays, we couldn't append values and all we could do is change its existing values. You can append values to a slice using two different methods.


2 Answers

dystroy explained it very well. I like to add a visual explanation to the behaviour.

A slice is only a descriptor of an array segment. It consists of a pointer to the array (ptr), the length of the segment (len), and capacity (cap).

    +-----+                                                              
    | ptr |                                                              
    |*Elem|                                                              
    +-----+                                                              
    | len |                                                              
    |int  |                                                              
    +-----+                                                              
    | cap |                                                              
    |int  |                                                              
    +-----+ 

So, the explanation of the code is as follow;

func main() {                                                           
                      +                                                 
                      |                                                 
  s := []int{5}       |  s -> +-----+                                   
                      | []int | ptr +-----> +---+                       
                      |       |*int | [1]int| 5 |                       
                      |       +-----+       +---+                       
                      |       |len=1|                                   
                      |       |int  |                                   
                      |       +-----+                                   
                      |       |cap=1|                                   
                      |       |int  |                                   
                      |       +-----+                                   
                      |                                                 
  s = append(s,7)     |  s -> +-----+                                   
                      | []int | ptr +-----> +---+---+                   
                      |       |*int | [2]int| 5 | 7 |                   
                      |       +-----+       +---+---+                   
                      |       |len=2|                                   
                      |       |int  |                                   
                      |       +-----+                                   
                      |       |cap=2|                                   
                      |       |int  |                                   
                      |       +-----+                                   
                      |                                                 
  s = append(s,9)     |  s -> +-----+                                   
                      | []int | ptr +-----> +---+---+---+---+           
                      |       |*int | [4]int| 5 | 7 | 9 |   |           
                      |       +-----+       +---+---+---+---+           
                      |       |len=3|                                   
                      |       |int  |                                   
                      |       +-----+                                   
                      |       |cap=4|                                   
                      |       |int  |                                   
                      |       +-----+                                   
                      |                                                 
  x := append(s,11)   |          +-------------+-----> +---+---+---+---+
                      |          |             | [4]int| 5 | 7 | 9 |11 |
                      |          |             |       +---+---+---+---+
                      |  s -> +--+--+  x -> +--+--+                     
                      | []int | ptr | []int | ptr |                     
                      |       |*int |       |*int |                     
                      |       +-----+       +-----+                     
                      |       |len=3|       |len=4|                     
                      |       |int  |       |int  |                     
                      |       +-----+       +-----+                     
                      |       |cap=4|       |cap=4|                     
                      |       |int  |       |int  |                     
                      |       +-----+       +-----+                     
                      |                                                 
  y := append(s,12)   |                        +-----> +---+---+---+---+
                      |                        | [4]int| 5 | 7 | 9 |12 |
                      |                        |       +---+---+---+---+
                      |                        |                        
                      |          +-------------+-------------+          
                      |          |             |             |          
                      |  s -> +--+--+  x -> +--+--+  y -> +--+--+       
                      | []int | ptr | []int | ptr | []int | ptr |       
                      |       |*int |       |*int |       |*int |       
                      |       +-----+       +-----+       +-----+       
                      |       |len=3|       |len=4|       |len=4|       
                      |       |int  |       |int  |       |int  |       
                      |       +-----+       +-----+       +-----+       
                      |       |cap=4|       |cap=4|       |cap=4|       
                      |       |int  |       |int  |       |int  |       
                      +       +-----+       +-----+       +-----+       
  fmt.Println(s,x,y)                                                    
} 
like image 150
Alper Avatar answered Oct 30 '22 15:10

Alper


A slice is only a window over part of an array, it has no specific storage.

This means that if you have two slices over the same part of an array, both slices must "contain" the same values.

Here's exactly what happens here :

  1. When you do the first append, you get a new slice of size 2 over an underlying array of size 2.
  2. When you do the next append, you get a new slice of size 3 but the underlying array is of size 4 (append usually allocates more space than the immediately needed one so that it doesn't need to allocate at every append).
  3. This means the next append doesn't need a new array. So x and y both will use the same underlying array as the precedent slice s. You write 11 and then 12 in the same slot of this array, even if you get two different slices (remember, they're just windows).

You can check that by printing the capacity of the slice after each append :

fmt.Println(cap(s))

If you want to have different values in x and y, you should do a copy, for example like this :

s := []int{5}
s = append(s, 7)
s = append(s, 9)
x := make([]int,len(s))
copy(x,s)
x = append(x, 11)
y := append(s, 12)
fmt.Println(s, x, y)

Another solution here might have been to force the capacity of the array behind the s slice to be not greater than the needed one (thus ensuring the two following append have to use a new array) :

s := []int{5}
s = append(s, 7)
s = append(s, 9)
s = s[0:len(s):len(s)]
x := append(s, 11)
y := append(s, 12)
fmt.Println(s, x, y)

See also Re-slicing slices in Golang

like image 36
Denys Séguret Avatar answered Oct 30 '22 13:10

Denys Séguret