gohugoio/hugo · error
elements must be comparable
Error message
elements must be comparable
What it means
Set-like collection functions in Hugo (union, intersect, symdiff, uniq — anything that builds an identity set via `collectIdentities`) must place each element in a Go map key. That requires the element's type to be comparable per Go's rules; slices, maps, and funcs (or structs containing them) are not, so Hugo rejects the operation.
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 8a468df065)
Solutions
- Operate on a comparable key extracted from each element instead of the whole element — e.g. use `where`, or build slices of strings/ids first (`{{ $ids := slice }}{{ range $items }}{{ $ids = $ids | append .id }}{{ end }}`), then apply uniq/intersect to those.
- For Pages collections, use the page-aware set functions on the Pages themselves (Pages are comparable via identity), not on derived maps.
- Restructure data files so the compared values are scalars (strings/numbers) rather than nested arrays/maps.
Example fix
<!-- before -->
{{ $u := uniq (slice (dict "id" 1) (dict "id" 1)) }}
<!-- after: dedupe by scalar key -->
{{ $ids := slice }}{{ range $items }}{{ $ids = $ids | append .id }}{{ end }}
{{ $u := uniq $ids }} Defensive patterns
Strategy: type-guard
Validate before calling
{{ $s := slice 1 2 3 }}
{{ $comparable := true }}
{{ range $s }}{{ if or (reflect.IsMap .) (reflect.IsSlice .) }}{{ $comparable = false }}{{ end }}{{ end }}
{{ if $comparable }}{{ uniq $s }}{{ end }} Type guard
{{ $isComparable := not (or (reflect.IsMap $el) (reflect.IsSlice $el)) }} Try / catch
{{ with try (uniq $s) }}{{ with .Err }}{{ warnf "uniq needs comparable elements: %s" . }}{{ else }}{{ .Value }}{{ end }}{{ end }} Prevention
- Functions like uniq, intersect, union, in require scalar (comparable) elements — not maps or slices
- When working with page or dict collections, extract a comparable key first (e.g. map to .Permalink or an id) before set operations
- Use where/complement for filtering collections of maps instead of set operations on the maps themselves
When it happens
Trigger: Calling `union`, `intersect`, `symdiff`, or `uniq` on collections whose elements are themselves slices or maps — e.g. `{{ symdiff (slice (slice 1 2)) (slice (slice 3)) }}` or deduplicating a slice of dicts/param maps.
Common situations: Trying to `uniq`/`intersect` slices of `dict` values or front-matter param maps; nested data from data files (arrays of arrays from JSON/YAML); comparing lists of taxonomy term maps instead of the term strings.
Related errors
- symdiff: failed to convert value: %w
- arguments to symdiff must be slices or arrays
- errWrongArgStructure
- errKeyIsEmptyString
- complement needs at least two arguments
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/96926240574b4ed8.json.
Report an issue: GitHub ↗.