Back to blog

Saturday, August 15, 2026

Why Are My Struct Fields Not Comparable with ==?

Why Are My Struct Fields Not Comparable with ==?

Why Are My Struct Fields Not Comparable with ==?

== on a struct walks every field. If any field's type is not comparable — slice, map, or function — the compiler rejects the comparison. A nil slice does not help; the type is what matters. That same rule is why the struct cannot be a map key.

The failure

package main

type User struct {
    Name string
    Tags []string
}

func main() {
    a := User{Name: "Ada", Tags: []string{"go"}}
    b := User{Name: "Ada", Tags: []string{"go"}}
    _ = a == b // invalid operation: struct containing []string cannot be compared
}

Name is fine. Tags poisons the type.

m := map[User]int{} // invalid map key type User

Why this happens

Go's == is defined for a closed set of types: booleans, numbers, strings, pointers, channels, interfaces (with a runtime panic if the dynamic type is incomparable), arrays of comparable types, and structs whose every field is comparable. Nested structs recurse. Arrays are comparable; slices are not. Arrays have a fixed length baked into the type. Slices are a header over a backing array — equality would have to invent a policy for length, capacity, and shared backing memory. The language refuses rather than guess.

Pointers compare as addresses, not as the values they point at. Two *int fields are equal when they name the same variable, not when both *p and *q are 42.

Omitted fields in a composite literal are still zero values ("", 0, nil). User{Name: "Ada"} is not "undefined Tags"; Tags is a nil slice. Equality, when it is allowed, includes those zeros.

The fix

Compare the fields you mean:

func sameUser(a, b User) bool {
    if a.Name != b.Name || len(a.Tags) != len(b.Tags) {
        return false
    }
    for i := range a.Tags {
        if a.Tags[i] != b.Tags[i] {
            return false
        }
    }
    return true
}

Or, in tests, reflect.DeepEqual(a, b) — it walks slices and maps. It is slower, has no compile-time check, and follows pointers. Use it in tests, not in a hot map lookup.

If the struct must be a map key, do not store a slice on it. Join tags into a string, use an array with a fixed max, or key by an ID:

type UserKey struct {
    Name string
    Tags string // strings.Join(tags, "\x00")
}

Keep the slice on a separate value type that you do not compare with ==.