fmt: map printing sort does not deterministically sort differing types
lukechampine opened this issue · 7 comments
What version of Go are you using (go version)?
$ go version go version go1.12 linux/amd64
Does this issue reproduce with the latest release?
Yes
What did you do?
fmt.Println(map[interface{}]string{
3: "3",
"a": "a",
2: "2",
"c": "c",
1: "1",
"b": "b"
})What did you expect to see?
1:1 2:2 3:3 a:a b:b c:cWhat did you see instead?
Non-deterministic result; differing types always compare to -1, so the result depends on map iteration, which is pseudo-random.
It's possible that I'm just misinterpreting the documentation here. The release notes say:
Interface values compare first by reflect.Type describing the concrete type and then by concrete value as described in the previous rules.
I read this as "values compare first lexicographically by type name, then by their concrete value." However, fmtsort.compare instead does:
c := compare(reflect.ValueOf(aType), reflect.ValueOf(bType))
if c != 0 {
return c
}Which results in the *reflect.rtype values being compared by their machine addresses. But since a and b have the same type, their addresses will be equal (not sure if this is always the case), so this compare is ineffectual.
I think the correct code implementation would be something like:
aConcreteType := aVal.Elem().Type().String()
bConcreteType := bVal.Elem().Type().String()
c := compare(reflect.ValueOf(aConcreteType), reflect.ValueOf(bConcreteType))
if c != 0 {
return c
}which produces deterministic output. I'm happy to submit a pull request for this if so desired.
Thank you @lukechampine for this report!
What does text/template do in this scenario? If the template package is deterministic and fmt isn't, that's very likely a bug to fix in fmt.
The result of go1.12 run for
package main
import (
"os"
"text/template"
)
func main() {
tmpl := `{{ range $k, $v := . }}Key:{{ $k }}, Value:{{ $v }}, {{ end }}`
t := template.New("hello")
mp := map[interface{}]string{
3: "3",
"a": "a",
2: "2",
"c": "c",
1: "1",
"b": "b",
}
tt, err := t.Parse(tmpl)
if err != nil {
panic(err)
}
if err = tt.Execute(os.Stdout, &mp); err != nil {
panic(err)
}
}
Key:1, Value:1, Key:2, Value:2, Key:a, Value:a, Key:b, Value:b, Key:c, Value:c, Key:3, Value:3
Change https://golang.org/cl/163745 mentions this issue: fmtsort: sort interfaces deterministically
@gopherbot, please open a backport issue for 1.12.
Backport issue(s) opened: #30484 (for 1.12).
Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://golang.org/wiki/MinorReleases.