将struct属性(切片)传递给从中删除元素的函数时的奇怪行为

I've started learning Go these days and got stuck in trying to pass a struct property's value (a slice) to a function. Apparently it's being passed as a reference (or it holds a pointer to its slice) and changes made inside the function affect it.

Here is my code, in which testFunction is supposed to receive a slice, remove its first 3 elements and print the updated values, but without affecting it externally:

package main

import (
    "fmt"
)

type testStruct struct {
    testArray []float64
}

var test = testStruct {
    testArray: []float64{10,20,30,40,50},
}

func main() {
    fmt.Println(test.testArray)
    testFunction(test.testArray)
    fmt.Println(test.testArray)
}

func testFunction(array []float64) {
    for i:=0; i<3; i++ {
        array = removeFrom(array, 0)
    }
    fmt.Println(array)
}

func removeFrom(array []float64, index int) []float64 {
    return append(array[:index], array[index+1:]...)
}

That outputs:

[10 20 30 40 50]
[40 50]
[40 50 50 50 50]

My question is: what is causing the third fmt.Println to print this strange result?

Playground: https://play.golang.org/p/G8W3H085In

p.s.: This code is only an example. It's not my goal to remove the first elements of something. I just wanna know what is causing this strange behaviour.

Usually we don't know whether a given call to append will cause a reallocation, so we can't assume that the original slice refers to the same array as the resulting slice, nor that it refers to a different one.

To use slices correctly, it's important to remember that although the elements of the underlying array are indirect, the slice's pointer, length and capacity are not.

As a result, it's usual to assign the result of a call to append to the same slice variable:

array = append(array, ...)

So to sum up, to receive the desired result always remember to assign the append function to a new or the same slice variable.

Here is the corrected and working code:

package main

import (
    "fmt"
)

type testStruct struct {
    testArray []float64
}

var test = testStruct {
    testArray: []float64{10,20,30,40,50},
}

func main() {
    fmt.Println(test.testArray)
    a := testFunction(test.testArray)
    fmt.Println(a)
}

func testFunction(array []float64)[]float64 {
    for i:=0; i<3; i++ {
        array = removeFrom(array, 0)
    }
    fmt.Println(array)
    return array
}

func removeFrom(array []float64, index int) []float64 {
    return append(array[:index], array[index+1:]...)
}

Check it the working code on Go Playground.


Another solution is to pass the array argument via pointer reference:

func testFunction(array *[]float64) {
    for i:=0; i<3; i++ {
        *array = removeFrom(*array, 0)
    }
    fmt.Println(*array)
}

Go Playground

The slice is a composite type. It has a pointer to the data, the length and the capacity. When you pass it as an argument you're passing those values, the pointer, the length and the capacity; they are copies, always.

In your case you modify the data within the slice when you call removeFrom(), which you can do because you've copied the value of a pointer to the original data into the func, but the length and capacity remain unchanged outside the scope of that function as those are not pointers.

So, when you print it again from main() you see the altered values but it still uses the original length and capacity as any changes made to those within the scope of the other funcs were actually on copies of those values.

Here is a useful blog post about slices https://blog.golang.org/slices. It states this in particular.

It's important to understand that even though a slice contains a pointer, it is itself a value. Under the covers, it is a struct value holding a pointer and a length. It is not a pointer to a struct.

The reason you see [40 50 50 50 50] is because you changed the values in the slice, but you did not alter the slice itself(it's cap and len)