Shanraq.org Shanraq.org
Maps in Go: key and value
IT

Go: from zero to your own blog Lesson 9 of 50

Maps in Go: key and value

The ninth lesson of the Go course. A map is a pair of key and value that finds an entry at once instead of walking a list. How to write and read one, why a missing key hands back zero rather than an error, what comma-ok is, why Go shuffles the iteration order on purpose, and how a nil map is more dangerous than a nil slice.

Why this matters

In the previous lesson the list of articles grew exactly as it should. But finding an article in it by address means walking the whole thing: a hundred articles, a hundred comparisons; a thousand, a thousand. And the browser has to be answered at once.

For that Go has a second way to hold many values — a map. The word is the same in the code, in the documentation and in the error messages, so there is only one term to learn here.

Picture it. A dictionary. You do not read it from the first page until you reach the word you want — you open it straight at the right letter. A list searches by walking; a dictionary answers at once.

The whole thing at once

A new folder, go mod init sabaq08, main.go:

package main

import (
	"fmt"
	"slices"
)

func main() {
	views := map[string]int{
		"go":      120,
		"kazakh":  45,
		"web":     30,
	}

	views["go"]++
	views["steppe"] = 7

	fmt.Println("tags:", len(views))
	fmt.Println("go:", views["go"])
	fmt.Println("music:", views["music"])

	n, ok := views["music"]
	fmt.Println("value:", n, "· key present:", ok)

	keys := make([]string, 0, len(views))
	for k := range views {
		keys = append(keys, k)
	}
	slices.Sort(keys)

	for _, k := range keys {
		fmt.Println(k, views[k])
	}
}

Before you run it, and without looking below, say out loud what it will print. Then run it and compare: the difference is exactly where you do not know the language yet.

go run . prints:

tags: 4
go: 121
music: 0
value: 0 · key present: false
go 121
kazakh 45
steppe 7
web 30

We never added a tag called “музыка” — and still got an answer.

Taking it apart

map[string]int — from what to what

The declaration reads left to right: inside the square brackets the type of the key, immediately after them the type of the value. map[string]int goes from a string to a number: a view count per tag.

Not everything can be a key, only what can be compared for equality: a string, a number, a bool. A slice cannot — precisely because Go refuses to compare two slices.

Writing and reading

views["steppe"] = 7      // written
n := views["go"]       // read
views["go"]++          // read, incremented, written back

The brackets are the ones you know from slices, but inside sits a key rather than a position. No append is needed: assigning to a new key is what adding means here.

The literal at the top of the program is the same thing written shorter: pairs separated by a colon, and a comma at the end of every line, the last one included.

A missing key is not an error, it is zero

views["music"] printed 0 even though no such tag exists. Go neither stopped the program nor complained: reading a key that is not there gives the zero value of the type in the declaration. For int that is 0, for string the empty string, for bool it is false.

Picture it. An empty shelf in a warehouse. You asked for the “музыка” shelf and were not told there is no such thing — you were shown an empty shelf. There is nothing on it, and there is no error either.

The counter in the exercise rests on this: m[tag]++ works for a tag that was never there, because it simply starts from zero.

Comma-ok: asking whether the key exists

Sometimes the difference matters: zero views and “no such tag” are two different things. Then you ask the map with two variables:

n, ok := views["music"]   // 0, false

The second value is true when the key is there and false when it is not. This form is called comma-ok, and you will meet it more than once.

The iteration order is not promised

range over a map walks every pair, but the order may come out different each time. This is not sloppiness but a decision: Go deliberately starts the walk at a different place so that nobody writes code that depends on the order.

See for yourself: walk the map without sorting and run the program ten times in a row — you will see several different orders.

Picture it. A deck shuffled before every deal. The cards are the same ones — but anyone who counted on their order is going to be wrong.

When you do need an order, collect the keys into a slice and sort them, as in the program above. slices.Sort lives in the standard library’s slices package — a name you know from the previous lesson. And note make([]string, 0, len(views)): length zero, capacity known in advance, not a single move.

delete and len

len(views) is how many pairs the map holds. delete(views, "web") removes a pair; if the key is not there nothing happens, and that is not an error either.

A nil map: reading is fine, writing is not

Here a map behaves unlike a slice, and the difference is worth remembering straight away.

var m map[string]int
fmt.Println(m["go"])   // 0 — reading is fine
m["go"] = 1            // panic: assignment to entry in nil map

In the previous lesson var titles []string took an append without complaint. A var m map[string]int will not take a write: the program stops. A map has to be created — make(map[string]int), or the literal map[string]int{}.

The lesson map

The lesson map: a key hands back its value, a missing key hands back zero

Say it in your own words

Without looking, answer out loud or on paper. The answers are at the end of the lesson.

  1. Why did views["music"] return zero instead of stopping the program? How do you tell “zero views” apart from “no such tag”?
  2. Why does Go deliberately vary the order in which a map is walked?
  3. var titles []string works with append, while var m map[string]int falls over on the very first write. What is the difference?

Exercise

Required. Write a function count(tags []string) map[string]int that counts how many times each tag appears. Call it on []string{"go", "web", "go", "steppe", "go", "web"} and print the result in alphabetical order of the keys — the map itself will not give you that order.

Optional.

  • has(m map[string]int, k string) bool, written with comma-ok.
  • Delete a key with delete and print len before and after.
  • Try writing into a var m map[string]int and read the whole crash message. Being able to read a panic is half of debugging.

Where this goes in your blog

Routing is a map: find the handler for an address without walking them all. A view counter per tag is a map. The three languages of one article are a map too: the text for each sits under the key kz, ru or en — that is exactly how the site you are reading is built. In the lesson on structs the value will stop being a number and become a whole article.

Build your own blog. This is where the last two lessons meet: the slice holds the order of the articles on the front page, and the maps find a title and a length by address. Neither could stand in for the other, and step-3 shows that line by line.

Answers

Show the answers
  1. Because reading a missing key in Go gives the zero value, which for int is zero. To tell the two apart, use the two-variable form: n, ok := views["music"], where ok is false when the key is absent.
  2. So that nobody comes to rely on an order the map never promised. If the order happened to be stable, programs would grow on top of it and break the first time the internals changed. When you need an order, collect the keys into a slice and sort them.
  3. An empty slice is ready for append: append creates the storage itself and returns a new slice. A map has no append, and a write goes straight into storage that does not exist yet, so the program stops. A map is created with make or with a literal.

Sources

If you have found a mistake or a typo in this article, tell us about it

Check your exercise

Solve it and run it in VS Code first — the editor shows the mistake where you made it. Paste the finished solution here. A model reads it: it will point at the mistake but will not hand you the answer.

Sign in to have it checked. Sign in

Comments (0)

No comments yet. Be the first.