gohugoio/hugo · error
elements must be comparable
Error message
elements must be comparable
What it means
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.
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.
Example fix
// before
{{ intersect $pageMaps1 $pageMaps2 }}
// after
{{ $keys1 := apply $pageMaps1 "index" "." "Title" }}
{{ $keys2 := apply $pageMaps2 "index" "." "Title" }}
{{ intersect $keys1 $keys2 }} Defensive patterns
Strategy: type-guard
Type guard
func elementsComparable(s any) bool {
v := reflect.ValueOf(s)
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { return false }
elem := v.Type().Elem()
if elem.Kind() == reflect.Pointer { elem = elem.Elem() }
return elem.Comparable()
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- argument must be a slice
- invalid intersect values
- arguments must be slices or arrays
- arguments to complement must be slices or arrays
- arguments to symdiff must be slices or arrays
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/96926240574b4ed8.
Report an issue: GitHub.
Appendix: source
Thrown at tpl/collections/reflect_helpers.go:67
return ip.TransientKey()
}
return vv
}
// collects identities from the slices in seqs into a set. Numeric values are normalized,
// pointers unwrapped.
func collectIdentities(seqs ...any) (map[any]bool, error) {
seen := make(map[any]bool)
for _, seq := range seqs {
v := reflect.ValueOf(seq)
switch v.Kind() {
case reflect.Array, reflect.Slice:
for i := range v.Len() {
ev, _ := hreflect.Indirect(v.Index(i))
if !ev.Type().Comparable() {
return nil, errors.New("elements must be comparable")
}
seen[normalize(ev)] = true
}
default:
return nil, fmt.Errorf("arguments must be slices or arrays")
}
}
return seen, nil
}
// We have some different numeric and string types that we try to behave like
// they were the same.
func convertValue(v reflect.Value, to reflect.Type) (reflect.Value, error) {
if v.Type().AssignableTo(to) {
return v, nil
}View on GitHub (pinned to 52c9bd7908)