Are slices passed by value?
Asked Answered
U

5

142

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?

Utas answered 12/10, 2016 at 8:15 Comment(0)
H
283

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: Performance of function slice parameter vs global variable?

Read blog post: Go Slices: usage and internals

Please note that when you pass a slice to a function, if the function modifies the "existing" elements of the slice, the caller will see / observe the changes. If the function adds new elements to the slice, that requires changing the slice header (the length at a minimum, but may also involve allocating a new backing array), which the caller will not see (not without returning the new slice header).

Not with maps, because maps are pointers under the hood, and if you pass a map to a function and the function adds a new entry to the map, the map pointer will not change so the caller will see the changed map (the new entry) without returning the map after change.

Also regarding slices and maps, see Map initialization in Go and why slice values can sometimes go stale but never map values?

Hildy answered 12/10, 2016 at 8:21 Comment(17)
so would a solution be to make a local copy of the slice inside the function. and editing that instead ?Utas
@user4901806 If you don't want to modify the elements of the passed slice (the elements of the backing array it points to), then yes, make a copy.Hildy
I'm assuming this would mean if we append an element, it wouldn't get added to the original slice.Sagesagebrush
@Sagesagebrush The slice header contains the length. If you append an element, the length must be increased, so the original slice header will not "see" it even if the backing array has room for this additional element and no new backing array is allocated and existing elements copied over. That's why the builtin append() function has to return a new slice value. Not to mention if a new array has to be allocated...Hildy
Doesnt arrays behave the same way ?Scrutineer
Also, would this mean that slices are always on the heap ?Scrutineer
@Scrutineer What do you mean they behave the same as arrays? And no, slices may be allocated on the stack if they don't "escape".Hildy
Is the SliceHeader then the memory layout for embedded slice types as well? Would type Container struct {data []byte} take up exactly the same amount of memory as SliceHeader?Earle
@vas type Container struct {data []byte} is not embedding, it's just a regular field. And if there is only a single field, then the answer is yes. If you have multiple fields, implicit padding may apply so the overall struct size may be bigger.Hildy
@Hildy yeah i know but a an anonymous field (embedding) example is hard to do in these comments! My understanding is that a non-pointer field and an anonymous field have the exact same memory representation. Right? Thanks for your answer!Earle
@vas Yes, whether you use embedding or a named field, they use the same amount of memory.Hildy
@Hildy dude thank you! I was working on a Hackerrank problem that had to deal with manipulating slices around and I could not for the life of me figure out why my function wasn't editing a copy of my slice by value. I'm new to the golang so I'm learning as I go. This saved me a lot of time and frustrationSavoy
It might enrich the answer to clarify "everything passed by value". Slices and maps are both passed by value, but are called "reference types" (blog.golang.org/maps). To add more confusion, there are differences in their reference-type behavior. For example, pass a slice from func A to func B, add a value to the slice, then func A will not see the added value. But pass a map from func A into func B, add to the map in func B, then func A will see the added value. Go play at play.golang.org/p/o9gm7JtDbMmPetes
@Josh Hibschman That's what I was thinking too! Glad I found your comment. This should be added to the answer here!Vellicate
@YannicHamann Please see this answer: Map initialization in Go. Adding a new entry to a map is not the same thing as adding a new element to a slice. Adding a new element to a map will add it to the same map structure (a map is a pointer under the hood and the pointer will not change). Adding an element to a slice involves changing the slice header (the length at a minimum, but may also require allocating a new backing array). Nonetheless, I've updated the answer.Hildy
a copy will be made from this header why then fmt.Printf("%p", sliceName) shows the same address in caller and inside called function? go.dev/play/p/6E5bjIXIGK7Wrestle
@Wrestle %p for slices prints the address of the 0th element, not the address of the slice header! See the correct "test" here: go.dev/play/p/TM4hR6cta10Hildy
G
11

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)
}
Glider answered 8/12, 2020 at 8:44 Comment(0)
K
7

Slice will work with pass by value to the function, But we should not use append to add values to slice in the function, instead we should use the assignment directly. Reason being that append will create new memory and copy values to that. Here is the example.

Go playground

     // Go program to illustrate how to
        // pass a slice to the function
        package main
        
        import "fmt"
        
        // Function in which slice
        // is passed by value
        func myfun(element []string) {
        
            // Here we only modify the slice
            // Using append function
            // Here, this function only modifies
            // the copy of the slice present in
            // the function not the original slice
            element = append(element, "blackhole")
            fmt.Println("Modified slice: ", element)
        }
        
        func main() {
        
            // Creating a slice
            slc := []string{"rocket", "galaxy", "stars", "milkyway"}
            fmt.Println("Initial slice: ", slc)
            //slice pass by value
            myfun(slc)
            fmt.Println("Final slice: ", slc)
        }
Output-
    Initial slice:  [rocket galaxy stars milkyway]
    Modified slice:  [rocket galaxy stars milkyway blackhole]
    Final slice:  [rocket galaxy stars milkyway]

Go Playground

    // Go program to illustrate how to
        // pass a slice to the function
        package main
        import "fmt"
        
        // Function in which slice
        // is passed by value
        func myfun(element []string) {
        
            // Here we only modify the slice
            // Using append function
            // Here, this function only modifies
            // the copy of the slice present in
            // the function not the original slice
            element[0] = "Spaceship"
            element[4] = "blackhole"
            element[5] = "cosmos"
            fmt.Println("Modified slice: ", element)
        }
        
        func main() {
        
            // Creating a slice
            slc := []string{"rocket", "galaxy", "stars", "milkyway", "", ""}
            fmt.Println("Initial slice: ", slc)
            //slice pass by value
            myfun(slc)
            fmt.Println("Final slice: ", slc)
        }
Output-
    Initial slice:  [rocket galaxy stars milkyway  ]
    Modified slice:  [Spaceship galaxy stars milkyway blackhole cosmos]
    Final slice:  [Spaceship galaxy stars milkyway blackhole cosmos]
Kulun answered 6/9, 2022 at 6:14 Comment(0)
P
5

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
}
Polymerous answered 6/6, 2020 at 11:50 Comment(0)
A
1

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

}
Amplitude answered 23/4, 2021 at 16:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.