Slice pass as value or pointer?

I got an interesting example which make me confused about how slice works as an argument.

When I pass a slice to the function, I can use that function to modify the element in the original slice but cannot append new element to it.

package main

import (
	"fmt"
)

func main() {
	var s = []string{"1", "2", "3"}
	modifySlice(s)
	fmt.Println(s[0])
	fmt.Println(s)
}

func modifySlice(i []string) {
	i[0] = "3"
	i = append(i, "4")
}

Result:
3
[3 2 3]

2 Likes

i found the answer from https://blog.golang.org/slices

the contents of a slice argument can be modified by a function, but its header cannot. The length stored in the slice variable is not modified by the call to the function, since the function is passed a copy of the slice header, not the original. Thus if we want to write a function that modifies the header, we must return it as a result parameter, just as we have done here.

3 Likes

Indeed. So to answer the question in the topic title, it’s idiomatic to have functions like slice = doSomethingWithSlice(slice) and less so to see doSomethingWithSlice(&slice). An exception is when you declare methods on a slice type (1) as those will by necessity have pointer-to-slice receivers if they intend to modify the slice;

type fibSlice []int

func (sp *fibSlice) append() {
	s := *sp
	l := len(s)

	switch l {
	case 0:
		*sp = append(s, 0)
	case 1:
		*sp = append(s, 1)
	default:
		*sp = append(s, s[l-1]+s[l-2])
	}
}

I avoided some of the incessant “starring” by the s := *sp but it still gets annoying rather quickly. It’s usually neater to just declare a struct type containing the slice if I’m going to do to much of this.

1 Like

It might be worth reading the internal details about what a slice actually is. When you pass a slice by value, your called function modifySlice gets a copy of the slice structure (a pointer and two ints), but that copy points to the same (possibly large) underlying array of storage as the slice in the calling code.

If you change the length of the slice inside the function, you’re just changing an int value in the slice structure, and that whole structure is thrown away when your function returns. But if you change one of the values in the underlying array, that change is potentially visible to all slices which point at the same underlying array, including the one in the calling code.

Even if doThing(&slice) were idiomatic I’d still avoid it, because functions with hidden side effects are generally a bad move. It’s cheap to pass slices by value, so you might as well do it.

2 Likes

Hey there,

So there are two things here, in the function modifySlice there’s two different access to the slice. The slice is, by definition, a pointer to an underlying array.

This code i[0] = "3" takes the position 0 in i and set it to "3", which is the original slice, since even when it doesn’t seem like it’s a pointer, it still is.

Here’s the issue: the append function makes a check… Quoting the docs:

If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated.

So when you do i = append(i, "4") you’re essentially saying: "add ‘4’ to this slice, but since the original “i” slice has reached its maximum capacity, create a new one, add “4” and then set it to “i”. Since “i” only exists within the modifySlice() function, you created a new slice that you’ll never return or use anymore.

Each slice has a length and a capacity. “The length of a slice is the number of elements it contains. The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.” So when you declared var s = []string{"1", "2", "3"}, you created a slice with length of 3 and capacity of 3. Changing one value that is already in the slice won’t create another slice, but appending to a slice that doesn’t have the capacity to hold more items will create a new one.

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.