Can you compare functions in Go? Why is a func only comparable to nil, and how does that affect using funcs as map keys?
Func values can only be compared to nil. f == g is a compile error ("func can only be compared to nil"), and map[func()]int is "invalid map key type". The reason: function equality has no clear meaning. Two closures from the same literal share code but capture different environments. The compiler may inline, deduplicate or specialize functions. Method values (t.M) allocate new closures. Any definition would either be surprising or would block optimizations.
What does this do?
f := func() {}
var a, b any = f, f
fmt.Println(a == b) // compiles, but panics at runtime:
// runtime error: comparing uncomparable type func()
m := map[any]int{}
m[f] = 1 // panics: runtime error: hash of unhashable type func()
Putting a func inside an interface moves the check from compile time to runtime, and the same applies to structs that contain func fields. Workarounds: key the map by a name or ID (map[string]Handler). Or wrap the func in a struct and use the pointer as identity (map[*Hook]struct{}), which is how you make "unsubscribe" work for callbacks. reflect.ValueOf(f).Pointer() returns the code pointer, but all closures from one literal share it, so it is not a reliable identity. Bonus: embedding _ [0]func() in a struct makes the struct non-comparable at zero size cost, which is useful for preventing == on types where it would be wrong.
More on Go Idioms, Design Patterns & Language Design
- Q552Explain closures in Go: what they capture, when captured variables escape to the heap, and how to build a generator or middleware with them.
- Q553Why does Go have no inheritance, function overloading, default arguments, ternary operator or exceptions? How do idiomatic Go programs work around each?
- Q555Does Go do tail-call optimization? What happens with deep recursion, and what does "goroutine stack exceeds 1000000000-byte limit" mean?
- Q556How do you structure a large Go project (cmd/, internal/, pkg/ debate)? How should packages be named and split to avoid stutter and cycles?
- Q557How do you do dependency injection in Go? Compare manual constructor wiring with google/wire and uber/fx.
- Q558How would you implement the decorator, strategy, and adapter patterns idiomatically in Go?