Are slices passed by value?

GoSlicePass by-Value

Go Problem Overview


In Go, I am trying to make a scramble slice function for my traveling salesman problem. While doing this I noticed when I started editing the slice I gave the scramble function was different every time I passed it in.

After some debugging I found out it was due to me editing the slice inside the function. But since Go is supposed to be a "pass by value" language, how is this possible?

https://play.golang.org/p/mMivoH0TuV

I have provided a playground link to show what I mean. By removing line 27 you get a different output than leaving it in, this should not make a difference since the function is supposed to make its own copy of the slice when passed in as an argument.
Can someone explain the phenomenon?

Go Solutions


Solution 1 - Go

Everything in Go is passed by value, slices too. But a slice value is a header, describing a contiguous section of a backing array, and a slice value only contains a pointer to the array where the elements are actually stored. The slice value does not include its elements (unlike arrays).

So when you pass a slice to a function, a copy will be made from this header, including the pointer, which will point to the same backing array. Modifying the elements of the slice implies modifying the elements of the backing array, and so all slices which share the same backing array will "observe" the change.

To see what's in a slice header, check out the reflect.SliceHeader type:

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

See related / possible duplicate question: https://stackoverflow.com/questions/33995634/are-golang-function-parameter-passed-as-copy-on-write

Read blog post: Go Slices: usage and internals

Solution 2 - Go

You can find an example below. Briefly slices is also passed by value but original slice and copied slice are linked to the same underlying array. If one of this slice changes, then underlying array changes, then other slice changes.

package main

import "fmt"

func main() {
	x := []int{1, 10, 100, 1000}
	double(x)
	fmt.Println(x) // ----> 3 will print [2, 20, 200, 2000] (original slice changed)
}

func double(y []int) {
	fmt.Println(y) // ----> 1 will print [1, 10, 100, 1000]
	for i := 0; i < len(y); i++ {
		y[i] *= 2
	}
	fmt.Println(y) // ----> 2 will print [2, 20, 200, 2000] (copy slice + under array changed)
}

Solution 3 - Go

Slices when its passed it’s passed with the pointer to underlying array, so a slice is a small structure that points to an underlying array. The small structure is copied, but it still points to the same underlying array. the memory block containing the slice elements is passed by "reference". The slice information triplet holding the capacity, the number of element and the pointer to the elements is passed by value.

The best way to handle slices passing to function (if the elements of the slice are manipulated into the function, and we do not want this to be reflected at the elements memory block is to copy them using copy(s, *c) as:

package main

import "fmt"

type Team []Person
type Person struct {
    Name string
    Age  int
}

func main() {
    team := Team{
        Person{"Hasan", 34}, Person{"Karam", 32},
    }
    fmt.Printf("original before clonning: %v\n", team)
    team_cloned := team.Clone()
    fmt.Printf("original after clonning: %v\n", team)
    fmt.Printf("clones slice: %v\n", team_cloned)
}

func (c *Team) Clone() Team {
    var s = make(Team, len(*c))
    copy(s, *c)
    for index, _ := range s {
        s[index].Name = "change name"
    }
    return s
}

But be careful, if this slice is containing a sub slice further copying is required, as we'll still have the sub slice elements sharing pointing to the same memory block elements, an example is:

type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
    Warehouse string
    Item      string
    Batches   Lots
}
type Lots []Lot
type Lot struct {
    Date  time.Time
    Key   string
    Value float64
}

func main() {
ins := Inventory{
        Warehouse: "DMM",
        Item:      "Gloves",
        Batches: Lots{
            Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
            Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 70},
        },
    }

   inv2 := CloneFrom(c Inventories)
}

func (i *Inventories) CloneFrom(c Inventories) {
    inv := new(Inventories)
    for _, v := range c {
        batches := Lots{}
        for _, b := range v.Batches {
            batches = append(batches, Lot{
                Date:  b.Date,
                Key:   b.Key,
                Value: b.Value,
            })
        }

        *inv = append(*inv, Inventory{
            Warehouse: v.Warehouse,
            Item:      v.Item,
            Batches:   batches,
        })
    }
    (*i).ReplaceBy(inv)
}

func (i *Inventories) ReplaceBy(x *Inventories) {
    *i = *x
}

Solution 4 - Go

To complement this post, here is an example of passing by reference for the Golang PlayGround you shared:

type point struct {
	x int
	y int
}

func main() {
	data := []point{{1, 2}, {3, 4}, {5, 6}, {7, 8}}
	makeRandomDatas(&data)
}

func makeRandomDatas(dataPoints *[]point) {
	for i := 0; i < 10; i++ {
		if len(*dataPoints) > 0 {
			fmt.Println(makeRandomData(dataPoints))
		} else {
			fmt.Println("no more elements")
		}
	}

}

func makeRandomData(cities *[]point) []point {
	solution := []point{(*cities)[0]}                 //create a new slice with the first item from the old slice
	*cities = append((*cities)[:0], (*cities)[1:]...) //remove the first item from the old slice
	return solution

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionduckView Question on Stackoverflow
Solution 1 - GoiczaView Answer on Stackoverflow
Solution 2 - GoArin YazilimView Answer on Stackoverflow
Solution 3 - GoHasan A YousefView Answer on Stackoverflow
Solution 4 - GoAl ElizaldeView Answer on Stackoverflow