In Go, map
is a built-in data type that represent hash table. In other programming languages, you might know map as dict
or associative array or a key/value store. If you're not familiar with such concept, map you can think map
like a dictionary, which every word is the key
and the definition is the element
of the map.
Before we begin, I'd like to point you to go spec for map and go blog for map to dig further into
map
.
Syntactically, map
looks like this:
map[KeyType]ElementType
KeyType
must be any comparable type, while ElementType
can be any valid type in Go, which means you can store anything from primitive variable to a slice.
It's important to remember that map
in Go is unordered, if you try to loop trough a map
and print the element, you might surprise yourself seeing that your elements printed in random order (give it a try if you like).
It is also important to know that each key is unique, meaning that assigning the same key twice will overwrite the value of the corresponding key.
map
is reference type, which means if you pass it around, Go won't copy the whole map. Instead what Go will do is copy the pointer of the map, this makes passing map to a function or variable cheap. The value of an uninitialized map is nil
.
You can define map as follows (we also called this a nil map);
var foo map[string]int
To initialize a map, you can do:
// With map literal
foo := map[string]int{}
or
// or with make function
foo := make(map[string]int)
A nil map is different from initialized map, writing to an nil map will cause a runtime error
panic: assignment to entry in nil map
Therefore it's important to initialize a map before using it
Here are some operations that you can do with a map
// Add a value in a map with the `=` operator:
foo["bar"] = 42
// Here we update the element of `bar`
foo["bar"] = 73
// To retrieve a map value, you can use
baz := foo["bar"]
// To delete an item from a map, you can use
delete(foo, "bar")
If you try to retrieve the value for a key which does not exist in the map, it will return the zero value of the value type.
This can confuse you, especially if the default value of your ElementType
(for example, 0 for an int), is a valid value.
To check whether a key exists in your map, you can use
value, exists := foo["baz"]
// If the key "baz" does not exist,
// value: 0; exists: false
As we've seen before, detecting whether a map
is initialized or not is easy, we can simply compare them to nil
but what about other maps? Well, since map
isn't a comparable, we can't use ==
operator, but reflect
package has something to rescue us, it's called DeepEqual
, we can use it like
import "reflect"
// ....
equal := reflect.DeepEqual(map[string]int{}, map[string]int{})
fmt.Println(equal)
// Output: true
But wait, if map isn't a comparable why are we able to compare them with nil
? Well, the spec has made an exception for this, see the comparable spec
The last one, if you're trying to write to a map
from multiple goroutines, that will trigger the race detector, see this link and here. Alternatively, you can use sync.Map
or atomic
or mutex
to work around this issue.