Search code examples
pointersgoslice

Cannot append to slice inside a function


I tried to add an element to my slice inside a function. I can change the element of the slice but cannot add a new element to it. Since slices act like reference why can't I add a new element to it?

Below is the code I have tried:

package main

import (
    "fmt"
)

func main() {
    a := []int{1, 2, 3}
    change(a)
    fmt.Println(a)
}
func change(a []int) {
    a[0] = 4
    a = append(a, 5)
}

Solution

  • Slice are pointers to underlying array. It is described in Golang:

    Map and slice values behave like pointers: they are descriptors that contain pointers to the underlying map or slice data. Copying a map or slice value doesn't copy the data it points to. Copying an interface value makes a copy of the thing stored in the interface value. If the interface value holds a struct, copying the interface value makes a copy of the struct. If the interface value holds a pointer, copying the interface value makes a copy of the pointer, but again not the data it points to.

    you are passing a copy of the slice not the original slice. Return the value after appending to the slice and then assign it to the original slice as

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        a := []int{1, 2, 3}
        a = change(a)
        fmt.Println(a)
    }
    
    func change(a []int) []int{
        a = append(a, 5)
        return a
    }
    

    Playground Example

    Or you can pass a pointer to slice of int but it is not recommended since slice it self is a pointer to bootstrap array.

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        a := []int{1, 2, 3}
        change(&a)
        fmt.Println(a)
    }
    
    func change(a *[]int){
        *a = append(*a, 5)
    }
    

    Note: Everything in Golang is pass by value.

    One thing to be considered is even if you are returning the updated slice and assigning to the same value, its original len and cap will change, which will lead to a new underlying array of different len. Try to print the length and cap before and after changing the slice to see the difference.

    fmt.Println(len(a), cap(a))
    

    The length is the number of elements referred to by the slice. The capacity is the number of elements in the underlying array (beginning at the element referred to by the slice pointer).

    Since the underlying array will check you can check it using reflect and unsafe for fetching the underlying array which is going to be different if cap of a slice change after appending data which is your case.

    package main
    
    import (
        "fmt"
        "reflect"
        "unsafe"
    )
    
    func main() {
        a := []int{1, 2, 3}
        hdr := (*reflect.SliceHeader)(unsafe.Pointer(&a))
        data := *(*[3]int)(unsafe.Pointer(hdr.Data))
        fmt.Println(data)
        a = change(a)
        hdr = (*reflect.SliceHeader)(unsafe.Pointer(&a))
        newData := *(*[4]int)(unsafe.Pointer(hdr.Data))
        fmt.Println(newData)
    }
    
    func change(a []int) []int {
        a = append(a, 5)
        return a
    }
    

    Playground Example

    This is the best part of slices that you need to worry about its capacity when appending data more than its capacity, since it will point to a new array allocated in the memory of bigger length.