Yolo Voe
Yolo Voe

Reputation: 505

Difference in behavior between slices and maps

A related questions is here https://stackoverflow.com/a/12965872/6421681.

In go, you can do:

func numsInFactorial(n int) (nums []int) {
    // `nums := make([]int)` is not needed
    for i := 1; i <= n; i++ {
        nums = append(nums, i)
    }
    return
}

However,the following doesn't work:

func mapWithOneKeyAndValue(k int, v int) (m map[int]int) {
    m[k] = v
    return
}

An error is thrown:

panic: assignment to entry in nil map

Instead, you must:

func mapWithOneKeyAndValue(k int, v int) map[int]int {
    m := make(map[int]int)
    m[k] = v
    return
}

I can't find the documentation for this behavior. I have read through all of effective go, and there's no mention of it there either.
I know that named return values are defined (i.e. memory is allocated; close to what new does) but not initialized (so make behavior isn't replicated).
After some experimenting, I believe this behavior can be reduced into understanding the behavior of the following code:

func main() {
    var s []int // len and cap are both 0
    var m map[int]int

    fmt.Println(s) // works... prints an empty slice
    fmt.Println(m) // works... prints an empty map

    s = append(s, 10) // returns a new slice, so underlying array gets allocated
    fmt.Println(s) // works... prints [10]

    m[10] = 10 // program crashes, with "assignment to entry in nil map"
    fmt.Println(m)
}

The issue seems that append likely calls make and allocates a new slice detecting that the capacity of s is 0. However, map never gets an explicit initialization.
The reason for this SO question is two-pronged. First, I would like to document the behavior on SO. Second, why would the language allow non-initializing definitions of slice and map? With my experience with go so far, it seems to be a pragmatic language (i.e. unused variables lead to compilation failure, gofmt forces proper formatting), so it would make sense for it to prevent the code from compiling.

Upvotes: 4

Views: 1021

Answers (2)

Mikhail
Mikhail

Reputation: 859

Try to assign in nil slice by index - you will get "panic: runtime error: index out of range" (example: https://play.golang.org/p/-XHh1jNyn5g)

The only reason why append function works with nil, is that append function can do reallocation for the given slice. For example, if you trying to to append 6th element to slice of 5 elements with current capacity 5, it will create the new array with new capacity, copy all the info from old one, and swap the data array pointers in the given slice. In my understanding, it is just golang implementation of dynamic arrays.

So, the nil slice is just a special case of slice with not enough capacity, so it would be reallocated on any append operation.

More details on https://blog.golang.org/go-slices-usage-and-internals

Upvotes: 5

ishaan
ishaan

Reputation: 2031

From https://blog.golang.org/go-maps-in-action

A nil map behaves like an empty map when reading, but attempts to write to a nil map will cause a runtime panic; don't do that. To initialize a map, use the built in make function

It seems like a nil map is considered a valid empty map and that's the reason they don't allocate memory for it automatically.

Upvotes: 3

Related Questions