hyperledger/fabric · error
receiver %T.%s returns %d values but expected 2
Error message
receiver %T.%s returns %d values but expected 2
What it means
Dispatch's contract is that a receiver method returns exactly two values: a proto.Message first and an error second, so it can marshal the response and propagate failures. This error is thrown when reflect reports NumOut() != 2, so the dispatcher cannot map the returns to its standard (response, error) shape.
Source
Thrown at core/dispatcher/dispatcher.go:44
// message type and it should return a proto message and error.
func (d *Dispatcher) Dispatch(inputBytes []byte, methodName string, receiver any) ([]byte, error) {
method := reflect.ValueOf(receiver).MethodByName(methodName)
if method == (reflect.Value{}) {
return nil, errors.Errorf("receiver %T.%s does not exist", receiver, methodName)
}
if method.Type().NumIn() != 1 {
return nil, errors.Errorf("receiver %T.%s has %d parameters but expected 1", receiver, methodName, method.Type().NumIn())
}
inputType := method.Type().In(0)
if inputType.Kind() != reflect.Pointer {
return nil, errors.Errorf("receiver %T.%s does not accept a pointer as its argument", receiver, methodName)
}
if method.Type().NumOut() != 2 {
return nil, errors.Errorf("receiver %T.%s returns %d values but expected 2", receiver, methodName, method.Type().NumOut())
}
if !method.Type().Out(0).Implements(reflect.TypeFor[proto.Message]()) {
return nil, errors.Errorf("receiver %T.%s does not return a an implementor of proto.Message as its first return value", receiver, methodName)
}
if !method.Type().Out(1).Implements(reflect.TypeFor[error]()) {
return nil, errors.Errorf("receiver %T.%s does not return an error as its second return value", receiver, methodName)
}
inputValue := reflect.New(inputType.Elem())
inputMsg, ok := inputValue.Interface().(proto.Message)
if !ok {
return nil, errors.Errorf("receiver %T.%s does not accept a proto.Message as its argument, it is '%T'", receiver, methodName, inputValue.Interface())
}
err := d.Protobuf.Unmarshal(inputBytes, inputMsg)
if err != nil {View on GitHub (pinned to 2736b63f8f)
Solutions
- Change the method to return exactly (protoMessage, error): func (s *Svc) Do(req *pb.Req) (*pb.Resp, error).
- If extra data must be returned, embed it as fields in the response proto message rather than adding another return value.
- If there is nothing meaningful to return, return an empty proto response message plus a nil error.
Example fix
// before
func (s *Svc) DeleteAsset(req *pb.DeleteRequest) error { ... }
// after
func (s *Svc) DeleteAsset(req *pb.DeleteRequest) (*pb.DeleteResponse, error) {
...
return &pb.DeleteResponse{}, nil
} Defensive patterns
Strategy: validation
Validate before calling
func validateTwoReturns(receiver any, methodName string) error {
m := reflect.ValueOf(receiver).MethodByName(methodName)
if !m.IsValid() {
return fmt.Errorf("method %s does not exist", methodName)
}
if m.Type().NumOut() != 2 {
return fmt.Errorf("method %s has %d returns, want (proto.Message, error)", methodName, m.Type().NumOut())
}
return nil
} Type guard
func returnsMessageAndError(receiver any, methodName string) bool {
m := reflect.ValueOf(receiver).MethodByName(methodName)
if !m.IsValid() || m.Type().NumOut() != 2 {
return false
}
return m.Type().Out(0).Implements(reflect.TypeFor[proto.Message]()) &&
m.Type().Out(1).Implements(reflect.TypeFor[error]())
} Prevention
- Standardize on the (proto message, error) return pair for every dispatched method.
- Validate all handler signatures once at service startup with a reflection sweep.
- Return auxiliary data inside the response proto message, not as extra return values.
When it happens
Trigger: Calling Dispatch (via Invoke) on a method returning only one value (e.g. just *pb.Response or just error), three values, or nothing at all.
Common situations: Handlers written like plain Go helpers returning only an error; returns in the wrong order like (error, *pb.Response); methods returning an extra bool or metadata value; ports of gRPC handlers with additional return values.
Related errors
- receiver %T.%s has %d parameters but expected 1
- receiver %T.%s does not accept a pointer as its argument
- receiver %T.%s does not return a an implementor of proto.Mes
- receiver %T.%s does not return an error as its second return
- message of type %s unknown
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/83f8986cddc163f8.
Report an issue: GitHub.