Slices as arguments in Golang

I recently came across several examples of using slices as function arguments, which lead to unexpected results. This post focuses on these examples.

First example. The slice is passed to the function as an argument where it will be modified.

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

package main

import "fmt"

func change(abc []int) {
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    change(abc)
    fmt.Println(abc)
}

Output:

[4 4 4]
[4 4 4]

Someone might expect that when we pass a slice to a function, we will get a copy of it in the function, and the changes will only appear in the function. But this is not the case. Slices in Go have a base array and are passed by reference, not value. We just pass the slice reference to the function, then change it, and it changes the outer slice.

Second example.

https://play.golang.org/p/5ruLrp6ZJJc

package main

import "fmt"

func change(abc []int) {
    abc = append(abc, 4)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    change(abc)
    fmt.Println(abc)
}

Output:

[4 4 4 4]
[1 2 3]

What changed? How is this example different from the first? We just add one element to the slice inside the function. But it changes the slice significantly. It creates a new slice. How? Well, when we create a slice outside function, it creates a base array of size for 3 elements. Then we pass its reference to the function. But when we want to add another element, we have no space for it in the underlying array. Then the built-in append function creates a new array and a new slice. When we change values in it, we do not change the first slice.

Third example.

https://play.golang.org/p/0tKWomkDCk3

package main

import "fmt"

func change(abc []int) {
    abc = append(abc, 5)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    change(abc)
    abc = append(abc, 4)
    fmt.Println(abc)
}

Output:

[4 4 4 4]
[1 2 3 4]

Why when we change slice outside function it will not point to changed slice in function? Well, it's just completly different slices - that's all. When we use append out of function it creates new array and new slice for it.

Fourth example.

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

package main

import "fmt"

func change(abc []int) {
    abc = append(abc, 4)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    abc = append(abc, 4)
    change(abc)
    fmt.Println(abc)
}

Output:

[4 4 4 4 4]
[4 4 4 4]

Why this example differs from third? When we append to slice before passing it to function it creates underlying array with capacity not just for one appended element but with for initial number of elements multiple 1,5 - for 6 elements. We can see it:

package main

import "fmt"

func change(abc []int) {
    abc = append(abc, 4)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
}

func main() {
    abc := []int{1, 2, 3}
    abc = append(abc, 4)
    fmt.Println(cap(abc))
    change(abc)
    fmt.Println(abc)
}

Output:

6
[4 4 4 4 4]
[4 4 4 4]

So when we add new element inside function we have enough space for it - that's why append will not create new array and new slice.

These examples can lead to unexpected results for someone. How we can avoid it?

Just return slice from function. It will return reference to new slice if it was created.

package main

import "fmt"

func change(abc []int) []int {
    abc = append(abc, 4)
    for i := range abc {
        abc[i] = 4
    }
    fmt.Println(abc)
    return abc
}

func main() {
    abc := []int{1, 2, 3}
    abc = change(abc)
    fmt.Println(abc)
}

Output:

[4 4 4 4]
[4 4 4 4]

And write unit tests. It will find unexpected behavior most likely. When applicable write tests first - use Test Driven Development.


Read also:


Comments

Popular posts from this blog

Methods for reading XML in Java

XML, well-formed XML and valid XML

ArrayList and LinkedList in Java, memory usage and speed