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

1

5
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.

Inoculate answered 20/8, 2021 at 18:24 Comment(0)
A
8

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

Aubree answered 20/8, 2021 at 18:38 Comment(16)
Thanks for your explanation. I am still confused that error(nil) gives nil, (*error)(nil) gives *errorInoculate
@WeiWong There's a difference between a nil interface value and a non-nil interface value wrapping a nil pointer. Check out Hiding nil values, understanding why golang fails hereAubree
I read the Question you mentioned. So error is an interface type, *error is not an interface type? I get that (*error)(nil) is a pointer of error type pointing to nil, but what about error(nil)? I am trying to understand why error(nil) gives <nil>Inoculate
It's in my answer, please read again. Passing (*error)(nil) will pass a non-nil interface{} value, wrapping a nil *error pointer. Passing error(nil) will pass a nil interface{} value, wrapping nothing. A nil interface value holds nothing.Aubree
error(nil) is a nil value of error interface, TypeOf(error(nil)) -> TypeOf(interface{error(nil)}) interface{error(nil)} became nil value of interface{} ? I don't know if my understanding is correct. Please correct me if I was wrong.Inoculate
If you pass error(nil), reflect.TypeOf() will receive a nil interface value. Again, this is in my answer.Aubree
@WeiWong: this is one of those cases where Go just isn't quite that simple: error is itself an interface type, so that error(nil) turns into the <nil,nil> pair at compile time. Other non-interface types don't have this issue, e.g., type T = *int makes T an alias for *int, so T(nil) turns into <*int,nil> (play.golang.org/p/XO1vjwk031U). It's critical to think of Go in terms of <type,value> pairs at all times.Benedikta
@Benedikta (*int)(nil)turns into<*int, nil> looks intuitive to me. interface{}(nil) turns into <nil, nil> does not. <?, nil> the value part turns into nil makes sense. nil is <type:nil, value:nil> when interface{}(nil) applies, interface is abstract type, so it turns to <type:nil, value:nil> ? Is that how should I understand it?Inoculate
More or less, yes. All interface values have a <type,value> pair at runtime. The type in the <type,value> pair is supplied by the compiler. When the compiler doesn't have a pre-provided type, the compiler provides nil instead. So any uninitialized variable of type interface I, for any I, is <nil,nil>. The Go authors then chose (for whatever reason) to say that, for any interface value v, v==nil is true if and only if v holds <nil,nil>. That leads to error(nil) "wanting" to be <nil,nil> rather than <error,nil>.Benedikta
If the Go authors had chosen that, at runtime, the v==nil test should test only the value part of v, not the type part, error(nil) could reasonably be the <error,nil> pair. That would be a different language, with other behaviors also being different; whether it would be better, worse, or equivalent is a matter of taste.Benedikta
@Benedikta Thank you. You should probably post this comment in Answer. It's really useful!Inoculate
@Benedikta If I want to determine if a method's return type is error interface or not. Is this the only way if method.Out(0) != reflect.TypeOf((*error)(nil)).Elem()? if method.Out(0) != reflect.TypeOf(error) why doesn't this code compile? func TypeOf(i interface{}) Type error is an interface, it should have work. linkInoculate
Let us continue this discussion in chat.Inoculate
The argument to reflect.TypeOf has to be a value (of type interface{}); error is a type (of type interface { ... } where the ... part is filled in). Other languages sometimes work around this distinction between "type" and "value" by decreeing that types are values (which leads to a different problem), but Go doesn't do that.Benedikta
@Benedikta Is there any other workaround for this expression if method.Out(0) != reflect.TypeOf((*error)(nil)).Elem() ? It looks ugly and unnatural. I took a weekend to figure it out. I wouldn't be able to write code like this to achieve my goal if I am about to write code on my own next time.Inoculate
Not sure what you mean by "workaround". That's the shortest way to write the expression, given what you've started with. It's not clear what your real goal is though—if your goal is to work with reflect, you've done that, and have achieved the minimal set of things to get what you want.Benedikta

© 2022 - 2024 — McMap. All rights reserved.