{"record":{"id":"96926240574b4ed8","repo":"gohugoio/hugo","slug":"elements-must-be-comparable","errorCode":null,"errorMessage":"elements must be comparable","messagePattern":"elements must be comparable","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tpl/collections/reflect_helpers.go","lineNumber":67,"sourceCode":"\t\treturn ip.TransientKey()\n\t}\n\n\treturn vv\n}\n\n// collects identities from the slices in seqs into a set. Numeric values are normalized,\n// pointers unwrapped.\nfunc collectIdentities(seqs ...any) (map[any]bool, error) {\n\tseen := make(map[any]bool)\n\tfor _, seq := range seqs {\n\t\tv := reflect.ValueOf(seq)\n\t\tswitch v.Kind() {\n\t\tcase reflect.Array, reflect.Slice:\n\t\t\tfor i := range v.Len() {\n\t\t\t\tev, _ := hreflect.Indirect(v.Index(i))\n\n\t\t\t\tif !ev.Type().Comparable() {\n\t\t\t\t\treturn nil, errors.New(\"elements must be comparable\")\n\t\t\t\t}\n\n\t\t\t\tseen[normalize(ev)] = true\n\t\t\t}\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"arguments must be slices or arrays\")\n\t\t}\n\t}\n\n\treturn seen, nil\n}\n\n// We have some different numeric and string types that we try to behave like\n// they were the same.\nfunc convertValue(v reflect.Value, to reflect.Type) (reflect.Value, error) {\n\tif v.Type().AssignableTo(to) {\n\t\treturn v, nil\n\t}","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/tpl/collections/reflect_helpers.go#L49-L85","documentation":"Hugo's `intersect`, `union`, and `symDiff` functions internally call `collectIdentities`, which builds a set of element identities from the input slices. Go's `reflect.Type.Comparable()` returns false for slices, maps, and funcs, so this error fires when those non-comparable types appear as elements. Numeric types are normalized and non-comparable structs are hashed, but raw slices/maps/funcs cannot be placed in a map key and are rejected.","triggerScenarios":"Calling `{{ intersect $a $b }}` (or `union`/`symDiff`) where one or both slices contain elements of a non-comparable type — e.g. a `[][]int`, a slice of maps, or a slice containing func values. The check at reflect_helpers.go:66–67 fires per element.","commonSituations":"A developer tries to intersect two collections of page Params (maps) or nested slices, expecting deep comparison. This is unsupported because Go maps and slices are not usable as map keys.","solutions":["Convert elements to a comparable scalar (e.g. extract an `.RelPermalink` or `.Title` string) before intersecting.","Use `collections.SymDiff`/`Intersect` only on slices of strings, numbers, or comparable structs.","If comparing maps, build a slice of a comparable key field and operate on that."],"exampleFix":"// before\n{{ intersect $pageMaps1 $pageMaps2 }}\n// after\n{{ $keys1 := apply $pageMaps1 \"index\" \".\" \"Title\" }}\n{{ $keys2 := apply $pageMaps2 \"index\" \".\" \"Title\" }}\n{{ intersect $keys1 $keys2 }}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func elementsComparable(s any) bool {\n    v := reflect.ValueOf(s)\n    if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { return false }\n    elem := v.Type().Elem()\n    if elem.Kind() == reflect.Pointer { elem = elem.Elem() }\n    return elem.Comparable()\n}","tryCatchPattern":null,"preventionTips":["Only intersect/union slices of comparable element types (strings, numbers, comparable structs).","Extract a comparable key (e.g. .Title, .RelPermalink) before set operations on complex objects.","Avoid intersecting slices of maps, slices, or funcs."],"tags":["hugo","template","collections","intersect","reflection"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}