Description
Motivation
Go currently supports NaN keys in maps through some clever tricks.
The current spec is ambiguous on this behavior. It says:
[I]f the map contains an entry with key
x
,a[x]
is the map value with keyx
and the type ofa[x]
is the value […]
but "with key x
" could plausibly mean "with key represented by x
". (In practice, today it means "with key equal to x
".)
That leads to surprising and subtle bugs (such as #14427) and adds complexity even in correct code (#20138 (comment)).
For example, the following two functions appear to be equivalent, but the latter version silently copies zeroes instead of the intended values for NaN keys:
func copy(m map[float64]int) map[float64]int {
c := make(map[float64]int, len(m))
for k, v := range m {
c[k] = v
}
}
func copy(m map[float64]int) map[float64]int {
c := make(map[float64]int, len(m))
for k := range m {
c[k] = m[k]
}
}
Proposal
We already reject incomparable values as map keys:
If the key type is an interface type, these comparison operators must be defined for the dynamic key values; failure will cause a run-time panic.
I propose that we should apply that standard to NaNs as well. Specifically, I propose that we add the following sentence to https://golang.org/ref/spec#Index_expressions:
Assigning to an element of a map with a key that is not equal to itself causes a run-time panic.
This proposal is along the lines of #19624, in that it produces panics for arithmetic operations which are otherwise likely to result in silent corruption in typical user code.
Workarounds
Users can easily avoid the proposed panics by calling checking whether the key is equal to itself before inserting a map entry.math.IsNaN(k)
Users who expect NaNs with the same representation to map to the same element can transform the keys using math.Float64bits
or math.Float32bits
:
if k == 0 { // -0.0 or +0.0
m[math.Float64bits(0)] = v
} else {
m[math.Float64bits(k)] = v
}
Users who really do intend to store all of the entries for NaN keys separately can explicitly pair the map with a slice:
type nanElem struct {
k float64
v V
}
var (
m map[float64]V
nans []nanElem
)
if k != k {
nans = append(nans, nanElem{k, v})
} else {
m[k] = v
}
Metadata
Metadata
Assignees
Type
Projects
Status