1

can someone help me understand what happens here?

package main

import (
    "fmt"
)

func appendString(slice []string, newString string) {
    slice = append(slice, newString)
}

func main() {
    slice := make([]string, 0, 1)
    appendString(slice, "a")
    fmt.Println(slice)
}

I know about the slice header and the need to use a pointer receiver. But here, as the underlying array has enough capacity I would expect append to work anyways (just adding the new value to the underlying array and the original [copied] header working as expected)

What is wrong with my assumptions?

Doppelganger
  • 20,114
  • 8
  • 31
  • 29
  • 2
    `append` always returns a new slice header, because the length field has to change. You are only thinking about the underlying *array* which doesn't need to be reallocated here. But the slice always changes. – Peter Oct 26 '18 at 13:24
  • 3
    @Peter Not always, only if more than 0 elements are appended. – icza Oct 26 '18 at 13:29
  • @Peter that was exactly my problem – Doppelganger Oct 26 '18 at 20:59

1 Answers1

5

Let's add a final print statement to see the result:

slice := make([]string, 0, 1)
fmt.Println(cap(slice))
appendString(slice, "a")
fmt.Println(slice)

And the output will be (try it on the Go Playground):

1
[]

Which is correct. One could expect the output to be:

1
[a]

The reason why this is not the case is because even though a new backing array will not be allocated, the slice header in the slice variable inside main() is not changed, it will still hold length = 0. Only the slice header stored in the slice local variable inside appendString() (the parameter) is changed, but this variable is independent from main's slice.

If you were to reslice main's slice, you will see that the backing array does contain the new string:

slice := make([]string, 0, 1)
fmt.Println(cap(slice))
appendString(slice, "a")
fmt.Println(slice)

slice = slice[:1]
fmt.Println(slice)

Now output will be (try it on the Go Playground):

1
[]
[a]

This is why the builtin append() has to return the new slice: because even if no new backing array is needed, the slice header (which contains the length) will have to be changed (increased) if more than 0 elements are appended.

This is why appendString() should also return the new slice:

func appendString(slice []string, newString string) []string {
    slice = append(slice, newString)
    return slice
}

Or short:

func appendString(slice []string, newString string) []string {
    return append(slice, newString)
}

Which you have to reassign where you use it:

slice := make([]string, 0, 1)
fmt.Println(cap(slice))
slice = appendString(slice, "a")
fmt.Println(slice)

And then you get the expected outcome right away (try it on the Go Playground):

1
[a]
icza
  • 389,944
  • 63
  • 907
  • 827