Go Maps

A Go map is a typed hash map data structure. A map’s type signature is of the form map[keyType]valueType where keyType and valueType are the types of the keys and values respectively.

To initialize a map, you must use the make function:

m := make(map[string]int)

An uninitialized map is equal to nil, and if read from or written a panic will occur at runtime.

The syntax for storing values is much the same as doing so with arrays or slices:

m["Alice"] = 21
m["Bob"] = 17

Similarly, retrieving values from a map is done like so:

a := m["Alice"]
b := m["Bob"]

You can use the range keyword to iterate over a map with a for loop:

for k, v := range m {
    fmt.Println(k, v)
}

This code will print:

Alice 21
Bob 17

Retrieving a value for a key that is not in the map will return the value type’s zero value:

c := m["Charlie"]
// c == 0

By reading multiple values from a map, you can test for a key’s presence. The second value will be a boolean indicating the key’s presence:

a, ok := m["Alice"]
// a == 21, ok == true
c, ok := m["Charlie"]
// c == 0, ok == false

To remove a key/value entry from a map, you flip it around and assign false as the second value:

m["Bob"] = 0, false
b, ok := m["Bob"]
// b == 0, ok == false

You can store arbitrary types in a map by using the empty interface type interface{}:

n := make(map[string]interface{})
n["One"] = 1
n["Two"] = "Two"

The only proviso is that when retrieving those values you must perform a type assertion to use them in their original form:

a := n["One"].(int)
b := n["Two"].(string)

You can use a type switch to determine the types of the values you’re pulling out, and deal with them appropriately:

for k, v := range n {
    switch u := v.(type) {
        case int:
            fmt.Printf("Key %q is an int with the value %v.\n", k, u)
        case string:
            fmt.Printf("Key %q is a string with the value %q.\n", k, u)
    }
}

Inside each of those case blocks, u will be of the type specified in the case statement; no explicit type assertion is necessary.

This code will print:

Key "One" is an int with the value 1.
Key "Two" is a string with the value "Two".

The key can be of any type for which the equality operator is defined, such as integers, floats, strings, and pointers. Interface types can also be used, as long as the underlying type supports equality. (Structs, arrays and slices cannot be used as map keys, because equality is not defined on those types.)

For example, the map o can take keys of any of the above types:

o := make(map[interface{}]int)
o[1] = 1
o["Two"] = 2

And that’s maps in a nutshell.

47582 views and 3 responses

  • Aug 22 2010, 11:21 AM
    drz responded:
    This is a great explanation of maps usage. What I would really like to see is a good explanation of how they are implemented (something similar to the articles about other data structures in http://research.swtch.com/). I can guess maps are more expensive than arrays, it is difficult to know when to use one.

    And one real question: how is determined the order in which maps values come when you use range?

  • Aug 23 2010, 3:59 PM
    Kevin Ballard responded:
    I haven't been paying attention to the go-nuts ML lately. Has there been any talk of extending the definition of equality to cover structs, arrays, and slices? The only real downside I can think of is the fact that these values are mutable, and mutating something that's the key of a map is a really bad idea.
  • Aug 24 2010, 3:23 AM
    Andrew Gerrand responded:
    drz: That's a great suggestion for a future blog post - I too am curious as to the exact implementation details. You can find the source in src/pkg/runtime/hashmap.{h,c} - there's a pretty good comment in the header file explaining the data structures.

    Kevin: There's been talk of it, but there are no plans to do so. You can just write a "Key() string" method for an arbitrary data type if you need to, so IMO it's not really that important.