Bitfield Consulting

View Original

Go maps FAQ

What is a map in Golang and how does it work? Are Go maps thread-safe? Are maps pointers? How do you check if a map is empty? Can maps be nil? How do you clear a map? How do you copy a map? How do you convert a map to JSON or YAML? Go teacher and expert John Arundel answers your frequently-asked questions about Go maps.

This is part 7 of a Golang tutorial series on maps. Check out the other posts in the series:

We've looked at the basics of Go maps in this series: declaring and initializing, reading and writing, iteration, and the very useful map[string]interface{} type. To wrap up, let's look at some of the most frequently-asked questions about Go maps, and try to answer them. If you have a question that's not answered here, please get in touch!

What is a map? How does a Golang map work?

When we introduced Go maps in the first part of this tutorial series, we saw that a map lets you relate a set of keys to a set of values. For example, a map[string]int relates a set of strings to a set of integers. It allows you to look up a string key, and find the corresponding integer value.

But how does this actually work, under the hood? Maps are implemented in Go as a hash table, which is a data structure designed to efficiently store and retrieve arbitrary values. The requirements and limitations of hash tables and hashing algorithms explain a lot about why maps in Go work the way they do.

Here are some great resources for learning more about Go's map implementation:

Are maps thread-safe?

Although we sometimes speak loosely about 'thread safety' in Go, goroutines are not threads, so we should really ask "Are maps in Go concurrency-safe?" instead.

The answer is no. As with any other Go variables (except channels), if you try to access a map from two or more goroutines at the same time, this will create a data race and cause your program to crash (at best). It's really important to be aware of this issue if you're writing concurrent code in Go, and to use the race detector to make sure you don't accidentally hit this problem.

One way to protect maps from concurrent access is to use a mutex (or a sync.Map, which has a built-in mutex), but it's often better to rearrange your program so that it doesn't need concurrent access to shared data in the first place (by using a channel, for example). A proverb says "Don't communicate by sharing memory, share memory by communicating".

Are maps pointers?

Yes and no. Maps, like slices, channels, and functions, are what's loosely called reference types. If we think about how data is actually stored in the computer's memory, then we might consider that variables are a bit like boxes: you can put values in them, and then retrieve them later by using the name of the box.

With so-called primitive types, like runes and integers, the contents of the box is just the value of the variable. With a reference type, though, the box actually contains a pointer; the address of another box, if you like. This makes it efficient to pass maps as parameters to functions, for example. We're not actually moving huge slabs of data around in memory, just a single 64-bit value which is the address of where the slab lives.

How do you check if a map is empty?

This is an easy one, because you can use the built-in len() function to check the number of keys in a map:

m := map[string]bool{
  "Go is awesome":             true,
  "I drink and I know things": true,
  "PI IS EXACTLY THREE!":      false,
}
fmt.Println(len(m))
// 3

So if the len() of the map is zero, the map is empty.

Can maps be nil?

Yes, they can. Any reference type can be nil, which is the same as saying that it doesn't point to any value in memory. A nil map is what you get when you declare a map variable, but don't initialize it:

var nilMap map[string]bool

Don't be confused by the difference between a nil map and an empty map. A nil map is one that has never been initialized, and can't be assigned to (this would terminate the program with a panic: assignment to entry in nil map error). By contrast, an empty map is a perfectly cromulent map which just happens to contain no keys. But if you're printing out the values, they look exactly the same:

var nilMap map[string]bool
emptyMap := map[string]bool{}
fmt.Println(nilMap, emptyMap)
// map[] map[]

The way to tell if a map value is nil is to compare it with nil:

fmt.Println(nilMap == nil)
// true

How do you clear a map?

While you can use the built-in delete() function for removing individual keys from a map, there's no built-in way to delete all keys from a map. Fortunately, that's rarely necessary, but if you do want to clear a Go map completely, the simplest way is to assign it an empty map literal:

m := map[string]bool{
  "Go is awesome":             true,
  "I drink and I know things": true,
  "PI IS EXACTLY THREE!":      false,
}
m = map[string]bool{}
fmt.Println(len(m))
// 0

How do you copy a map?

This is completely straightforward: use a for loop (see Iterating over Golang maps for more about this) and copy each key and value to the new map.

c := map[string]bool{}
for k, v := range m {
  c[k] = v
}

How do you convert a map to JSON?

Too easy! Use the encoding/json library:

import "encoding/json"
...
m := map[string]bool{
  "Go is awesome":             true,
  "I drink and I know things": true,
  "PI IS EXACTLY THREE!":      false,
}
data, err := json.Marshal(m)
if err != nil {
  log.Fatal(err)
}
fmt.Printf("%s\n", data)
// {"Go is awesome":true,"I drink and I know things":true,"PI IS EXACTLY THREE!":false}

How do you convert a map to YAML?

There's a library for that too:

import "gopkg.in/yaml.v2"
...
m := map[string]bool{
  "Go is awesome":             true,
  "I drink and I know things": true,
  "PI IS EXACTLY THREE!":      false,
}
data, err := yaml.Marshal(m)
if err != nil {
  log.Fatal(err)
}
fmt.Printf("%s\n", data)
// Go is awesome: true
// I drink and I know things: true
// PI IS EXACTLY THREE!: false

How do you I can't even

If I didn't answer your question about Go maps here, let me know! Or you can tweet your question to me at @bitfield on Twitter, or email go@bitfieldconsulting.com, and I'll do my best to answer it.

If you haven't seen the previous posts in this tutorial series, check them out! Start with part 1: Declaring and initializing maps in Go

Next

You're now a master of maps! Nice work. Why not check out some of my other Go tutorials?

You can read more about how to use maps in real Go applications in my book, For the Love of Go. You'll also learn all about structs and slices, while developing the core of an online bookstore app in Go.

Looking for help with Go?

If you found this article useful, and you'd like to learn more about Go, then I can help! I offer one-to-one or group training in Go development, for complete beginners through to experienced Gophers. Check out my Learn Golang with mentoring page to find out more, or email go@bitfieldconsulting.com. Looking forward to hearing from you!

Gopher image by egonelbre

See this content in the original post