Why a plain lookup isn't enough
In Go, reading a key that isn't in a map does not fail. It gives the zero value of the value type: 0 for an int, "" for a string, false for a bool. That is convenient, but it means the value alone can't tell you whether the key exists. A map that holds "X": "" and a map with no "X" both answer "" to m["X"].
So a check like capitals["Spain"] == "" is unreliable. It is true for a missing key, but also for a key whose value really is an empty string.
The comma-ok lookup
A lookup can return two values: the value and a bool that is true only when the key is in the map. By convention the second one is named ok.
capitals := map[string]string{"France": "Paris", "Japan": "Tokyo"}
if _, ok := capitals["Japan"]; ok {
fmt.Println(capitals["Japan"]) // Tokyo
}
if _, ok := capitals["Spain"]; !ok {
fmt.Println("No Spain") // No Spain
}
Three details are worth knowing:
- The
_throws the value away. This check only needsok, and Go would reject an unused variable, so the blank identifier is the way to ignore it. - The lookup can sit in the
ifline, before a;. It runs first, andokexists only inside thatif(and itselse). !okasks the opposite question: it is true when the key is missing.
With a map that stores an empty string, the difference shows. For empty := map[string]string{"X": ""}, the lookup v, ok := empty["X"] gives "" and true, while a lookup of "Y" gives "" and false. The comparison == "" is true for both, so only ok separates them.
When you need the value as well, keep it instead of throwing it away. The value and ok come from the same lookup, so there is no second search:
if capital, ok := capitals["Japan"]; ok {
fmt.Println("The capital is", capital) // The capital is Tokyo
} else {
fmt.Println("Unknown")
}
For "Peru" the same code prints Unknown.
A value is not a key
A lookup looks only at the keys. capitals["Paris"] gives an ok of false, because "Paris" is a value, not a key. Keys must also match exactly, upper and lower case included, so capitals["japan"] misses as well. Checking a value is a different job and needs a loop over the map.
The task: pets and their kinds
In pets, each key is a pet's name and its value is the kind of animal. The task has two checks.
- If
"Tom"is a key, printTom is afollowed by Tom's kind. Use the comma-ok lookup in theifline, and print"Tom is a " + pets["Tom"]inside. - Check that
"cat"is not a key, with!ok, and printcat is not a key."cat"does appear in the map, but only as a value: it is Tom's kind. The lookup looks at keys only, so it misses.
pets := map[string]string{"Rex": "dog", "Tom": "cat", "Polly": "parrot"}
if _, ok := pets["Tom"]; ok {
fmt.Println("Tom is a " + pets["Tom"])
}
if _, ok := pets["cat"]; !ok {
fmt.Println("cat is not a key")
}
The output is:
Tom is a cat
cat is not a key
Common mistakes
- Checking
pets["cat"]for a name when"cat"is a kind. It is a value, so the lookup misses. - Using the value in the
ifcondition instead ofok. A stored zero value then looks like a missing key. - Using
okafter theifblock. A variable declared in theifline isn't visible outside it.
Key takeaways
- A missing key gives the zero value, so only
oktells a missing key from a stored zero value. if _, ok := m[k]; ok { ... }runs when the key is there; use!okwhen it isn't.- Lookups check keys, never values, and keys match exactly.
