Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass slice as function argument, and modify the original slice

Tags:

go

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 } 
like image 405
Lansana Camara Avatar asked Mar 22 '18 12:03

Lansana Camara


People also ask

How do you pass a slice to a function?

As we know that the slice pointer always points to the same reference even if they passed in a function. So, when we change the value C to Java present at index value 2. This change reflects the slice present outside the function too, so the final slice after modification is [C# Python Java perl].

Is slice passed by value or reference?

When we pass a slice to a function as an argument the values of the slice are passed by reference (since we pass a copy of the pointer), but all the metadata describing the slice itself are just copies.

How do you append to a slice?

Since slices are dynamically-sized, you can append elements to a slice using Golang's built-in append method. The first parameter is the slice itself, while the next parameter(s) can be either one or more of the values to be appended.

How do you copy a slice in Golang?

To duplicate a slice in Go, getting a deep copy of its contents, you need to either use the built-in copy() function, or create a new empty slice and add all the elements of the first slice to it using the append() function.


1 Answers

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.

like image 82
AndrewS Avatar answered Oct 09 '22 07:10

AndrewS