49

I know everything is passed by value in Go, meaning if I give a slice to a function and that function appends to the slice using the builtin append function, then the original slice will not have the values that were appended in the scope of the function.

For instance:

nums := []int{1, 2, 3}

func addToNumbs(nums []int) []int {
    nums = append(nums, 4)
    fmt.Println(nums) // []int{1, 2, 3, 4}
}

fmt.Println(nums) // []int{1, 2, 3}

This causes a problem for me, because I am trying to do recursion on an accumulated slice, basically a reduce type function except the reducer calls itself.

Here is an example:

func Validate(obj Validatable) ([]ValidationMessage, error) {
    messages := make([]ValidationMessage, 0)

    if err := validate(obj, messages); err != nil {
        return messages, err
    }

    return messages, nil
}

func validate(obj Validatable, accumulator []ValidationMessage) error {
    // If something is true, recurse
    if something {
        if err := validate(obj, accumulator); err != nil {
            return err
        }
    }

    // Append to the accumulator passed in
    accumulator = append(accumulator, message)

    return nil
}

The code above gives me the same error as the first example, in that the accumulator does not get all the appended values because they only exist within the scope of the function.

To solve this, I pass in a pointer struct into the function, and that struct contains the accumulator. That solution works nicely.

My question is, is there a better way to do this, and is my approach idiomatic to Go?

Updated solution (thanks to icza):

I just return the slice in the recursed function. Such a facepalm, should have thought of that.

func Validate(obj Validatable) ([]ValidationMessage, error) {
    messages := make([]ValidationMessage, 0)
    return validate(obj, messages)
}

func validate(obj Validatable, messages []ValidationMessage) ([]ValidationMessage, error) {
    err := v.Struct(obj)

    if _, ok := err.(*validator.InvalidValidationError); ok {
        return []ValidationMessage{}, errors.New(err.Error())
    }

    if _, ok := err.(validator.ValidationErrors); ok {
        messageMap := obj.Validate()

        for _, err := range err.(validator.ValidationErrors) {
            f := err.StructField()
            t := err.Tag()

            if v, ok := err.Value().(Validatable); ok {
                return validate(v, messages)
            } else if _, ok := messageMap[f]; ok {
                if _, ok := messageMap[f][t]; ok {
                    messages = append(messages, ValidationMessage(messageMap[f][t]))
                }
            }
        }
    }

    return messages, nil
}
Lansana Camara
  • 9,497
  • 11
  • 47
  • 87
  • 4
    Return the new slice, just like the builtin `append()` does (and assign back the return value at the caller), or pass a pointer to the slice, not the slice header. – icza Mar 22 '18 at 12:30
  • I can't return the new slice since I must work off the slice for recursion, but I think passing a pointer to the slight might work? Let me try that.. – Lansana Camara Mar 22 '18 at 12:31
  • 3
    Recursive calls can also return values, I don't see a problem in that. If you go with passing slice pointers, note that handling them is more "verbose", see [Slicing a slice pointer passed as argument](https://stackoverflow.com/questions/38013922/slicing-a-slice-pointer-passed-as-argument/38014097#38014097) as an example. – icza Mar 22 '18 at 12:33
  • 1
    You know what, you're totally right. Don't know what I was thinking. Returning the slice from the recursive function worked nicely for me. Thanks a lot. – Lansana Camara Mar 22 '18 at 12:36
  • @icza the `validate(obj Validatable, messages []ValidationMessage)` manipulate a copy of `messages` not the "real" `messages`. So every time your return, you return a copy of the old slice. Wouldn't it be better if he passes the address of the slice?! `validate(obj Validatable, messages *[]ValidationMessage)`. This way he can change the content directly: `*messages = append(*messages, ValidationMessage(messageMap[f][t]))`. – elmiomar Mar 22 '18 at 16:33
  • @OmarIlias Don't get fooled by initial "impressions". Check [golang slices pass by value?](https://stackoverflow.com/questions/39993688/are-golang-slices-pass-by-value/39993797#39993797) and [Are Golang function parameter passed as copy-on-write?](https://stackoverflow.com/questions/33995634/are-golang-function-parameter-passed-as-copy-on-write/33995762#33995762) TLDR: passing slices is idiomatic and preferred compared to passing pointers to slices. – icza Mar 22 '18 at 16:37
  • @icza there are no initial "impressions" here. Before posting my comment I made sure I read [this](https://blog.golang.org/slices) twice. So, giving pointers to answers that say the same and that you wrote won't change those initial "impressions". Thanks anyway! – elmiomar Mar 22 '18 at 17:12
  • 1
    @OmarIlias Passing a slice header is fast enough as it's small. If you pass a pointer to it, passing might be faster, but in order to use it, you have to dereference it "constantly" (on each use) and code will be uglier, so you lose your advantage. Just think about it: the builtin `append()` could have been written to take a pointer and it wouldn't need to return the new slice (and you wouldn't have to assign back the result), yet it went down on the other path to require a slice and not a pointer to it. – icza Mar 22 '18 at 17:19

4 Answers4

67

If you want to pass a slice as a parameter to a function, and have that function modify the original slice, then you have to pass a pointer to the slice:

func myAppend(list *[]string, value string) {
    *list = append(*list, value)
}

I have no idea if the Go compiler is naive or smart about this; performance is left as an exercise for the comment section.

For junior coders out there, please note that this code is provided without error checking. For example, this code will panic if list is nil.

AndrewS
  • 8,196
  • 5
  • 39
  • 53
  • Actually, it is apparently faster than accessing an array as a global variable - https://stackoverflow.com/a/64841088/9805867 – IvanD Nov 15 '20 at 04:04
17

Slice grows dynamically as required if the current size of the slice is not sufficient to append new value thereby changing the underlying array. If this new slice is not returned, your append change will not be visible.

Example:

package main

import (
    "fmt"
)

func noReturn(a []int, data ...int) {
    a = append(a, data...)
}

func returnS(a []int, data ...int) []int {
    return append(a, data...)
}

func main() {
    a := make([]int, 1)
    noReturn(a, 1, 2, 3)
    fmt.Println(a) // append changes will not visible since slice size grew on demand changing underlying array

    a = returnS(a, 1, 2, 3)
    fmt.Println(a) // append changes will be visible here since your are returning the new updated slice
}

Result:

[0]
[0 1 2 3]

Note:

  1. You don't have to return the slice if you are updating items in the slice without adding new items to slice
vedhavyas
  • 1,101
  • 6
  • 12
  • 1
    Thanks, this is a good explanation, however it does not offer a solution to the problem :) nevertheless, I've already found a solution by just returning the slice in the recursive function, then I don't have to try operating on a pointer to a slice or pass a pointer struct containing the slice around. – Lansana Camara Mar 22 '18 at 12:53
  • 8
    "You don't have to return the slice if your slice was initialized with size enough to append new values." This is false. If you append to a slice, even if the underlying array has sufficient space for the appended values, the appended values will not be present in the calling function's slice because the slice header also contains length and capacity fields, and those won't be updated by the append. You _can_ access them by subslicing beyond the length of the slice, but they won't just automatically show up. Example: https://play.golang.org/p/z1mIuIDdMkJ – Kaedys Mar 22 '18 at 14:38
15

Slice you passed is an reference to an array, which means the size is fixed. If you just modified the stored values, that's ok, the value will be updated outside the called function.

But if you added new element to the slice, it will reslice to accommodate new element, in other words, a new slice will be created and old slice will not be overwritten.

As a summary, if you need to extend or cut the slice, pass the pointer to the slice.Otherwise, use slice itself is good enough.

Update


I need to explain some important facts. For adding new elements to a slice which was passed as a value to a function, there are 2 cases:

A

the underlying array reached its capacity, a new slice created to replace the origin one, obviously the origin slice will not be modified.

B

the underlying array has not reached its capacity, and was modified. BUT the field len of the slice was not overwritten because the slice was passed by value. As a result, the origin slice will not aware its len was modified, which result in the slice not modified.

hao
  • 639
  • 5
  • 11
  • 2
    This is really good explaination which solve the issue confusing me a lot in golang – Stan Aug 23 '22 at 15:11
2

When appending data into slice, if the underlying array of the slice doesn't have enough space, a new array will be allocated. Then the elements in old array will be copied into this new memory, accompanied with adding new data behind

razvan
  • 31
  • 2
  • 1
    This explains why the append operation doesn't always modify the original passed slice, even though slices are "reference types" due to the headers of the underlying arrays being pointers. +1 for explanation, could have done better with this comment in terms of implementation/examples though – George Edward Shaw IV Feb 09 '21 at 08:28