The Refactor That FailedYou’re finally upgrading a legacy Go service to use generics. You decide to start small with a utility function that checks if a slice contains a specific value. The logic is simple, so you write this:
func Contains[T any](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
You run go build, expecting a success message. Instead, the compiler blocks you with a frustrating error:
./main.go:10:10: invalid operation: v == target (type T does not satisfy comparable)
Why Go is Blocking Your BuildThe any keyword is an alias for interface{}. It is a common mistake to assume any means 'supports all operations.' In reality, Go’s compiler is incredibly protective. It won't let you use the equality operator (==) unless it can guarantee the type supports it.
Not every type in Go is comparable. Slices, maps, and functions are the primary offenders. If you passed a slice of slices into the function above, the expression v == target would trigger a runtime panic. To prevent this, Go requires you to be explicit about your requirements for T.
The Quick Fix: The 'comparable' ConstraintThe fastest solution is to swap any for the built-in comparable constraint. This constraint limits T to types that work with == and !=. This includes booleans, numbers, strings, pointers, and channels. It also includes structs, provided all their fields are also comparable.
The Corrected Code```
// Swap [T any] for [T comparable] func Contains[T comparable](slice []T, target T) bool { for _, v := range slice { if v == target { return true } } return false }
Now the compiler is happy. If you try to pass `[]int`, it works. If you try to pass `[][]string`, the compiler will catch the mistake at build time rather than letting your app crash in production.
## Handling Non-Comparable TypesWhat if you need to compare complex data? If your struct contains a slice or a map, the `comparable` constraint won't work. In these cases, you should follow the pattern established in Go 1.21’s `slices` package: pass a comparison function.
### Example: Using a Comparison Function```
func ContainsFunc[T any](slice []T, target T, equals func(T, T) bool) bool {
for _, v := range slice {
if equals(v, target) {
return true
}
}
return false
}
// Usage with a non-comparable struct
type User struct {
ID int
Tags []string // Slices make this struct non-comparable
}
users := []User{{ID: 1, Tags: []string{"admin"}}, {ID: 2}}
target := User{ID: 1}
found := ContainsFunc(users, target, func(a, b User) bool {
return a.ID == b.ID
})
Verification and TestingFollow these steps to ensure your fix is robust:
- Verify the Signature: Check that your generic parameter is
[T comparable].- Run Your Suite: Executego test ./.... If it compiles, your types satisfy the constraint.- Test the Bounds: Try passing a slice of maps ([]map[string]string) to the function. You should see a clear compiler error. This confirms the constraint is doing its job.## SummaryThetype T does not satisfy comparableerror is just Go's way of asking for a type safety guarantee. Usecomparablefor simple types like strings or integers. For complex types like slices or maps, skip the equality operator and use a custom comparison function instead.

