Which types can be map keys? What happens with interface keys and NaN?
Question 63HardGo 1.22 to 1.25
Keys must be comparable: booleans, numbers, strings, pointers, channels, interfaces, and arrays or structs made only of comparable types. Slices, maps, and functions cannot be keys. Two edge cases trip people up:
// 1) Interface keys: compile-time OK, runtime panic if the dynamic type is not hashable
m := map[any]int{}
m[1] = 1
m["x"] = 2
// m[[]int{1}] = 3 // panic: runtime error: hash of unhashable type []int
// 2) NaN keys: NaN != NaN, so every insert creates a new entry
f := map[float64]int{}
nan := math.NaN()
f[nan] = 1
f[nan] = 2
_, ok := f[nan]
fmt.Println(len(f), ok) // 2 false -- unreachable by lookup
delete(f, nan) // no-op!
for k, v := range f { fmt.Println(k, v) } // NaN 1, NaN 2 (range still sees them)
clear(f) // the ONLY way to remove NaN keys (Go 1.21)
fmt.Println(len(f)) // 0
// +0.0 and -0.0 are equal, so they are the same key
f[0.0] = 1
f[math.Copysign(0, -1)] = 2
fmt.Println(len(f)) // 1
Other notes:
- Struct keys such as
map[struct{X, Y int}]booland array keys such asmap[[2]int]boolwork well for composite keys and are better than concatenating strings. - Pointer keys compare by identity, not by content.
- In generics, the
comparableconstraint accepts interface types since Go 1.20, so a generic map can still panic at run time.
More on Arrays, Slices, Maps & Strings
- Q61Why does m["k"].Field = 1 fail to compile for map[string]T where T is a struct?
- Q62Why can't you take the address of a map element (&m[k]), when you can with slice elements?
- Q64Do Go maps shrink after deleting entries? How do you reclaim memory?
- Q65What does the built-in clear() do for slices vs maps?
- Q66Explain nil map behaviour and what happens when you pass a map to a function.
- Q67Why are strings immutable in Go, and what does converting between string and []byte cost?