Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is returning a slice of a local array in a Go function safe?

What happens if I return a slice of an array that is a local variable of a function or method? Does Go copy the array data into a slice create with make()? Will the capacity match the slice size or the array size?

func foo() []uint64 {
    var tmp [100]uint64
    end := 0
    ...
    for ... {
        ...
        tmp[end] = uint64(...)
        end++
        ...
    }
    ... 
    return tmp[:end]
} 
like image 638
chmike Avatar asked Mar 01 '17 11:03

chmike


People also ask

Is a slice an array in Go?

Slices are similar to arrays, but are more powerful and flexible. Like arrays, slices are also used to store multiple values of the same type in a single variable. However, unlike arrays, the length of a slice can grow and shrink as you see fit.

Can we return a local array?

A local array cannot be directly returned from a C++ function as it may not exist in memory after the function call. A way to resolve this is to use a static array in the function. As the lifetime of the static array is the whole program, it can easily be returned from a C++ function without the above problem.

What is difference between array and slice in Go?

Slices in Go and Golang The basic difference between a slice and an array is that a slice is a reference to a contiguous segment of an array. Unlike an array, which is a value-type, slice is a reference type. A slice can be a complete array or a part of an array, indicated by the start and end index.

Does Golang pass slice by 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.


2 Answers

This is detailed in Spec: Slice expressions.

The array will not be copied, but instead the result of the slice expression will be a slice that refers to the array. In Go it is perfectly safe to return local variables or their addresses from functions or methods, the Go compiler performs an escape analysis to determine if a value may escape the function, and if so (or rather if it can't prove that a value may not escape), it allocates it on the heap so it will be available after the function returns.

The slice expression: tmp[:end] means tmp[0:end] (because a missing low index defaults to zero). Since you didn't specify the capacity, it will default to len(tmp) - 0 which is len(tmp) which is 100.

You can also control the result slice's capacity by using a full slice expression, which has the form:

a[low : high : max]

Which sets the resulting slice's capacity to max - low.

More examples to clarify the resulting slice's length and capacity:

var a [100]int

s := a[:]
fmt.Println(len(s), cap(s)) // 100 100
s = a[:50]
fmt.Println(len(s), cap(s)) // 50 100
s = a[10:50]
fmt.Println(len(s), cap(s)) // 40 90
s = a[10:]
fmt.Println(len(s), cap(s)) // 90 90

s = a[0:50:70]
fmt.Println(len(s), cap(s)) // 50 70
s = a[10:50:70]
fmt.Println(len(s), cap(s)) // 40 60
s = a[:50:70]
fmt.Println(len(s), cap(s)) // 50 70

Try it on the Go Playground.

Avoiding heap allocation

If you want to allocate it on the stack, you can't return any value that points to it (or parts of it). If it would be allocated on the stack, there would be no guarantee that after returning it remains available.

A possible solution to this would be to pass a pointer to an array as an argument to the function (and you may return a slice designating the useful part that the function filled), e.g.:

func foo(tmp *[100]uint64) []uint64 {
    // ...
    return tmp[:end]
}

If the caller function creates the array (on the stack), this will not cause a "reallocation" or "moving" to the heap:

func main() {
    var tmp [100]uint64
    foo(&tmp)
}

Running go run -gcflags '-m -l' play.go, the result is:

./play.go:8: leaking param: tmp to result ~r1 level=0
./play.go:5: main &tmp does not escape

The variable tmp is not moved to heap.

Note that [100]uint64 is considered a small array to be allocated on the stack. For details see What is considered "small" object in Go regarding stack allocation?

like image 56
icza Avatar answered Sep 21 '22 21:09

icza


Nothing wrong happens.

Go does not make a copy but compiler performs escape analysis and allocates the variable that will be visible outside function on heap.

The capacity will be the capacity of underlying array.

like image 43
Grzegorz Żur Avatar answered Sep 20 '22 21:09

Grzegorz Żur