Search code examples
gogo-reflect

what does reflect.TypeOf((*error)(nil)).Elem()` mean?


func (s *service) registerMethods() {
    s.method = make(map[string]*methodType)
    for i := 0; i < s.typ.NumMethod(); i++ {
        method := s.typ.Method(i)
        mType := method.Type
        if mType.NumIn() != 3 || mType.NumOut() != 1 {
            continue
        }
        if mType.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
            continue
        }
        argType, replyType := mType.In(1), mType.In(2)
        if !isExportedOrBuiltinType(argType) || !isExportedOrBuiltinType(replyType) {
            continue
        }
        s.method[method.Name] = &methodType{
            method:    method,
            ArgType:   argType,
            ReplyType: replyType,
        }
        log.Printf("rpc server: register %s.%s\n", s.name, method.Name)
    }
}

what does reflect.TypeOf((*error)(nil)).Elem() mean in this code? I know if mType.Out(0) != reflect.TypeOf((*error)(nil)).Elem() is trying to determine if the method's return type is error not not. But for me, reflect.TypeOf((error)(nil)) intuitively will do the same, but unfortunately not. When I try to compile this code, it says type error is not an expression, what does it mean in this context? Does not reflect.Typeof() accepts a argument of certain type? I found that (*error)(nil) is equivalent to *error = nil. I am confused with this expression.


Solution

  • TL;DR; reflect.TypeOf((*error)(nil)).Elem() is an expression used to obtain the reflect.Type type descriptor of the interface type error. Using reflect.TypeOf(error(nil)) cannot be used for the same purpose (read the why below).


    reflect.TypeOf((*error)(nil)).Elem() achieves its goal by using a typed nil pointer value of type *error, passing it to reflect.TypeOf() to get the reflect.Type descriptor of the type *error, and uses Type.Elem() to get the type descriptor of the element (base) type of *error, which is error.

    reflect.TypeOf() expects an interface{} value:

    func TypeOf(i interface{}) Type
    

    Basically whatever value you pass to reflect.TypeOf(), if it's not already an interface value, it will be wrapped in an interface{} implicitly. If the passed value is already an interface value, then the concrete value stored in it will be passed as interface{}.

    So if you try to pass an error value to it, since error is an interface type, the concrete value stored in it will be "repacked" into an interface{} value. The interface type error will not be retained / transferred!

    If you pass a nil value of type error, e.g. error(nil), since the interface value itself is nil, it contains no concrete value and type, a nil interface{} value will be passed, that will result in nil reflect.Type returned. Quoting from reflect.TypeOf():

    TypeOf returns the reflection Type that represents the dynamic type of i. If i is a nil interface value, TypeOf returns nil.

    If you pass a value of type *error (which may be a nil pointer), it's not an interface value, it's a pointer value (a pointer to interface). So it will be wrapped in an interface{} value, and the concrete value stored in it will be of type *error. Using Type.Elem() you can access the pointed type, that is error.

    This is one of the rare cases when using a pointer to interface makes sense, and in fact inevitable.

    See related questions:

    Get the reflect.Kind of a type which is based on a primitive type

    What is the difference between reflect.ValueOf() and Value.Elem() in go?

    Hiding nil values, understanding why golang fails here