Ask questions

In the Go source code library or other open source projects, you’ll find some functions that use a slice argument that use a pointer to the slice type instead of the slice type. Why not pass the slice directly to the underlying array data? What’s the difference between the two?

For example, in the source log package, the Logger object is bound to the formatHeader method, which takes an input object buf of type *[]byte, not []byte.

func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {}
Copy the code

Here are some examples

func modifySlice(innerSlice []string) {
	innerSlice[0] = "b"
	innerSlice[1] = "b"
	fmt.Println(innerSlice)
}

func main(a) {
	outerSlice := []string{"a"."a"}
	modifySlice(outerSlice)
	fmt.Print(outerSlice)
}

// The output is as follows
[b b]
[b b]
Copy the code

Let’s change the input type of the modifySlice function to a pointer to the slice

func modifySlice(innerSlice *[]string) {
	(*innerSlice)[0] = "b"
	(*innerSlice)[1] = "b"
	fmt.Println(*innerSlice)
}

func main(a) {
	outerSlice := []string{"a"."a"}
	modifySlice(&outerSlice)
	fmt.Print(outerSlice)
}

// The output is as follows
[b b]
[b b]
Copy the code

Well, in the example above, both functions pass the same parameter type, and there seems to be no difference. Passing it through a pointer seems useless, and in any case the slice is passed by reference, and in both cases the slice is modified.

This confirms what we have always known: changes to slices inside the function will affect slices outside the function. But is it really so?

Textual research and interpretation

In the article “Do you Really understand the conversion between String and []byte”, we talked about the underlying structure of slicing as shown below.

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}
Copy the code

Array is a pointer to the underlying array, with len representing length and cap representing capacity.

Let’s make the following minor changes to the example above.

func modifySlice(innerSlice []string) {
	innerSlice = append(innerSlice, "a")
	innerSlice[0] = "b"
	innerSlice[1] = "b"
	fmt.Println(innerSlice)
}

func main(a) {
	outerSlice := []string{"a"."a"}
	modifySlice(outerSlice)
	fmt.Print(outerSlice)
}

// The output is as follows
[b b a]
[a a]
Copy the code

What’s amazing is that the changes to the slice inside the function don’t affect the external slice?

To make it clear what’s going on, add more detail to the print.

func modifySlice(innerSlice []string) {
	fmt.Printf("%p %v %p\n", &innerSlice, innerSlice, &innerSlice[0])
	innerSlice = append(innerSlice, "a")
	innerSlice[0] = "b"
	innerSlice[1] = "b"
	fmt.Printf("%p %v %p\n", &innerSlice, innerSlice, &innerSlice[0])}func main(a) {
	outerSlice := []string{"a"."a"}
	fmt.Printf("%p %v %p\n", &outerSlice, outerSlice, &outerSlice[0])
	modifySlice(outerSlice)
	fmt.Printf("%p %v %p\n", &outerSlice, outerSlice, &outerSlice[0])}// The output is as follows
0xc00000c060 [a a]   0xc00000c080
0xc00000c0c0 [a a]   0xc00000c080
0xc00000c0c0 [b b a] 0xc000022080
0xc00000c060 [a a]   0xc00000c080
Copy the code

In the Go function, the parameter passing of the function is all value passing. So, passing the slice to the function as a parameter essentially copies the slice structure object, and the field values of both slice structures are equal. Normally, since the array in the function’s slice structure and the array in the outside of the function’s slice structure point to the same underlying array, any change to the data in the underlying array affects both.

The problem is that if the pointer to the underlying array is overwritten or modified (copy, reallocation, append triggering expansion), then the internal changes to the data in the function will not affect the external slice, and neither len (the length) nor cap (the capacity) will be modified.

To make this clear to the reader, visualize the above process as follows.

As you can see, when the length and capacity of the slice are equal, append occurs, triggering the expansion of the slice. During expansion, a new underlying array will be created, and the data in the original array will be copied to the new array, and the appended data will also be placed in the new array. The sliced array pointer points to the new underlying array. So, the relationship between the inside slice and the outside slice has been severed completely, and its change has no effect on the outside slice.

Note that slice expansion is not always equal-fold expansion. In case readers get confused, here’s a brief description of the growslice expansion principle (source code: growslice in SRC/Runtime /slice.go) :

When the required slice capacity is more than twice the original slice capacity, the required slice capacity is used as the new capacity. Otherwise, when the original slice length is less than 1024, the new slice capacity is directly doubled. When the original slice capacity is greater than or equal to 1024, it is repeatedly increased by 25% until the new slice capacity exceeds the required capacity.

Now we know why some functions that use a slice as an argument need to take a pointer to the slice type instead of the slice type.

func modifySlice(innerSlice *[]string) {
	*innerSlice = append(*innerSlice, "a")
	(*innerSlice)[0] = "b"
	(*innerSlice)[1] = "b"
	fmt.Println(*innerSlice)
}

func main(a) {
	outerSlice := []string{"a"."a"}
	modifySlice(&outerSlice)
	fmt.Print(outerSlice)
}

// The output is as follows
[b b a]
[b b a]
Copy the code

Keep in mind that you can pass slices by value if you only want to change the value of the element in the slice, without changing the size and pointing of the slice. Otherwise, you should consider passing slices by pointer.

Sample to consolidate

To see if you really understand the above question, I have made two variations of the above example that you can test for yourself.

Testing a

func modifySlice(innerSlice []string) {
	innerSlice[0] = "b"
  innerSlice = append(innerSlice, "a")
	innerSlice[1] = "b"
	fmt.Println(innerSlice)
}

func main(a) {
	outerSlice := []string{"a"."a"}
	modifySlice(outerSlice)
	fmt.Println(outerSlice)
}
Copy the code

Test two

func modifySlice(innerSlice []string) {
	innerSlice = append(innerSlice, "a")
	innerSlice[0] = "b"
	innerSlice[1] = "b"
	fmt.Println(innerSlice)
}

func main(a) {
	outerSlice:= make([]string.0.3)
	outerSlice = append(outerSlice, "a"."a")
	modifySlice(outerSlice)
	fmt.Println(outerSlice)
}
Copy the code

Answer to Test one

[b b a]
[b a]
Copy the code

Answer to Test two

[b b a]
[b b]
Copy the code

Did you do it right?