vitessio/vitess · error
listener must be a function
Error message
listener must be a function
What it means
event.AddListener reflects on the listener function to validate it takes exactly one input argument matching the dispatched event type; a listener that is not a func at all is rejected immediately with BadListenerError panic. Since this is a programming error, the library panics rather than returning an error.
Source
Thrown at go/event/event.go:111
func (why BadListenerError) Error() string {
return "bad listener func: " + string(why)
}
// AddListener registers a listener function that will be called when a matching
// event is dispatched. The type of the function's first (and only) argument
// declares the event type (or interface) to listen for.
func AddListener(fn any) {
listenersMutex.Lock()
defer listenersMutex.Unlock()
fnType := reflect.TypeOf(fn)
// check that the function type is what we think: # of inputs/outputs, etc.
// panic if conditions not met (because it's a programming error to have that happen)
switch {
case fnType.Kind() != reflect.Func:
panic(BadListenerError("listener must be a function"))
case fnType.NumIn() != 1:
panic(BadListenerError("listener must take exactly one input argument"))
}
// the first input parameter is the event
evType := fnType.In(0)
// keep a list of listeners for each event type
listeners[evType] = append(listeners[evType], fn)
// if eventType is an interface, store it in a separate list
// so we can check non-interface objects against all interfaces
if evType.Kind() == reflect.Interface {
interfaces = append(interfaces, evType)
}
}
// Dispatch sends an event to all registered listeners that were declaredView on GitHub (pinned to 01a25a7d17)
Solutions
- Pass a function taking exactly one parameter of the event type, e.g. func(ev *MyEvent)
- Check the value being registered is a func (see type guard below)
- Fix the call site so the handler signature matches the event's documented contract
Example fix
// before
event.AddListener(listeners, myStruct)
// after
event.AddListener(listeners, func(ev *myEvent) { /* handle */ }) Defensive patterns
Strategy: type-guard
Validate before calling
func safeAddListener(ls event.Listeners, fn any) {
if reflect.TypeOf(fn) != nil && reflect.TypeOf(fn).Kind() == reflect.Func {
event.AddListener(ls, fn)
}
} Type guard
func isFuncListener(fn any) bool {
t := reflect.TypeOf(fn)
return t != nil && t.Kind() == reflect.Func
} Try / catch
defer func() {
if r := recover(); r != nil {
if _, ok := r.(event.BadListenerError); ok {
log.Errorf("invalid listener: %v", r)
} else {
panic(r)
}
}
}()
event.AddListener(listeners, candidate) Prevention
- Always register handlers with signature func(*EventType)
- Type-check listener candidates in wrappers before AddListener
- Recover BadListenerError panics in plugin/dynamic registration paths
When it happens
Trigger: Calling event.AddListener(listeners, x) where x is not a function — e.g. a struct, nil, or a value of non-func type passed by mistake.
Common situations: Passing a method value bound incorrectly; passing the event type instead of a handler; mixing up AddListener with a generic subscribe API that accepts callbacks of any shape.
Related errors
- input is not a slice
- this command panics on purpose
- failed to load flag for %s: %w
- uncaught panic: %v
- QueryRowsMap unexpected error: %+v
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/d9b0796714fdfa11.
Report an issue: GitHub.