vitessio/vitess · error

listener must take exactly one input argument

Error message

listener must take exactly one input argument

What it means

event.AddListener panics when a registered listener function does not take exactly one input parameter. Listeners are dispatched via reflection, so the framework requires a strict signature: a single argument (the event). This is a programming error, so the library panics at registration time rather than returning an error.

Source

Thrown at go/event/event.go:113

	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 declared
// to accept values of the event's type, or interfaces that the value implements.
func Dispatch(ev any) {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Change the listener function to accept exactly one parameter whose type is the event type.
  2. If extra data is needed, capture it in a closure around a single-argument function.
  3. Verify the value passed to AddListener is the function itself, not the result of calling it or a bound method with extra args.

Example fix

// before
event.AddListener(func(ev *MyEvent, seq int) {
    handle(ev, seq)
})
// after
seq := 0
event.AddListener(func(ev *MyEvent) {
    seq++
    handle(ev, seq)
})
Defensive patterns

Strategy: validation

Validate before calling

fnVal := reflect.ValueOf(listener)
if fnVal.Kind() != reflect.Func || fnVal.Type().NumIn() != 1 {
    panic(fmt.Sprintf("listener %T must be a function with exactly one input", listener))
}
event.AddListener(listener)

Type guard

func isValidListener(fn interface{}) bool {
    t := reflect.TypeOf(fn)
    return t != nil && t.Kind() == reflect.Func && t.NumIn() == 1
}

Prevention

When it happens

Trigger: Calling event.AddListener with a function whose signature has zero inputs, or two or more inputs (e.g. func(ev *Event, extra string) or func()).

Common situations: Writing a listener that also wants context, a sender parameter, or extra configuration arguments; accidentally passing the wrong function (e.g. a handler meant for another framework); refactoring a listener signature without updating AddListener calls.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/694497e92706c341. Report an issue: GitHub.