As far as I understand, types slice
and map
are similar in many ways in Go. They both reference
(or container
) types. In terms of abstract data types, they represent an array and an associative array, respectively.
However, their behaviour is quite different.
var s []int
var m map[int]int
While we can use a declared slice immediately (append new items or reslice it), we cannot do anything with a newly declared map. We have to call make
function and initialize a map explicitly. Therefore, if some struct contains a map we have to write a constructor function for the struct.
So, the question is why it is not possible to add some syntaсtic sugar and both allocate and initialize the memory when declaring a map.
I did google the question, learnt a new word "avtovivification", but still failing to see the reason.
I am not talking about struct literal. Yes, you can explicitly initialize a map by providing values such as m := map[int]int{1: 1}
. However, if you have some struct:
package main
import (
"fmt"
)
type SomeStruct struct {
someField map[int]int
someField2 []int
}
func main() {
s := SomeStruct{}
s.someField2 = append(s.someField2, -1) // OK
s.someField[0] = -1 // panic: assignment to entry in nil map
fmt.Println(s)
}
It is not possible to use a struct immediately (with default values for all fields). One has to create a constructor function for SomeStruct
which has to initialize a map explicitly.
While we can use a declared slice immediately (append new items or reslice it), we cannot do anything with a newly declared map. We have to call
make
function and initialize a map explicitly. Therefore, if some struct contains a map we have to write a constructor function for the struct.
That's not true. Default value–or more precisely zero value–for both slices and maps is nil
. You may do the "same" with a nil
map as you can do with a nil
slice. You can check length of a nil
map, you can index a nil
map (result will be the zero value of the value type of the map), e.g. the following are all working:
var m map[int]int
fmt.Println(m == nil) // Prints true
fmt.Println(len(m)) // Prints 0
fmt.Println(m[2]) // Prints 0
Try it on the Go Playground.
What you "feel" more about the zero-value slice is that you may add values to it. This is true, but under the hood a new slice will be allocated using the exact make()
builtin function that you'd have to call for a map in order to add entries to it, and you have to (re)assign the returned slice. So a zero-value slice is "no more ready for use" than a zero-value map. append()
just takes care of necessary (re)allocation and copying over. We could have an "equivalent" addEntry()
function to which you could pass a map value and the key-value pairs, and if the passed map is nil
, it could allocate a new map value and return it. If you don't call append()
, you can't add values to a nil
slice, just as you can't add entries to a nil
map.
The primary reason that the zero value for slices and maps is nil
(and not an initialized slice or map) is performance and efficiency. It is very often that a map or slice value (either variable or a struct field) will never get used, or not right away, and so if they would be allocated at declaration, that would be a waste of memory (and some CPU) resources, not to mention it gives more job to the garbage collector. Also if the zero value would be an initialized value, it would often be insufficient (e.g. a 0-size slice cannot hold any elements), and often it would be discarded as you add new elements to it (so the initial allocation would be a complete waste).
Yes, there are cases when you do want to use slices and maps right away, in which cases you may call make()
yourself, or use a composite literal. You may also use the special form of make()
where you supply the (initial) capacity for maps, avoiding future restructuring of the map internals (which usually requires non-negligible computation). An automatic non-nil
default value could not guess what capacity you'd require.
You can! What you're looking for is:
package main
import "fmt"
func main() {
v := map[int]int{}
v[1] = 1
v[2] = 2
fmt.Println(v)
}
:=
is declare and assign, where as var
is simply declare.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With