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

Wei Wong
  • 65
  • 1
  • 7

1 Answers1

7

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

icza
  • 389,944
  • 63
  • 907
  • 827
  • Thanks for your explanation. I am still confused that `error(nil) ` gives `nil`, `(*error)(nil) ` gives `*error` – Wei Wong Aug 21 '21 at 02:17
  • @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 here](https://stackoverflow.com/questions/29138591/hiding-nil-values-understanding-why-golang-fails-here/29138676#29138676) – icza Aug 21 '21 at 07:48
  • 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 `` – Wei Wong Aug 21 '21 at 09:52
  • 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. – icza Aug 21 '21 at 10:14
  • `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. – Wei Wong Aug 21 '21 at 11:07
  • 1
    If you pass `error(nil)`, `reflect.TypeOf()` will receive a `nil` interface value. Again, this is in my answer. – icza Aug 21 '21 at 11:27
  • 1
    @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 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>` (https://play.golang.org/p/XO1vjwk031U). It's critical to think of Go in terms of pairs at all times. – torek Aug 21 '21 at 15:21
  • @torek `(*int)(nil)`turns into`<*int, nil>` looks intuitive to me. `interface{}(nil)` turns into `` does not. `, nil>` the value part turns into nil makes sense. `nil` is `` when `interface{}(nil)` applies, interface is abstract type, so it turns to ? Is that how should I understand it? – Wei Wong Aug 21 '21 at 15:46
  • 1
    More or less, yes. All interface *values* have a pair at runtime. The type in the 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 ``. 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 ``. That leads to `error(nil)` "wanting" to be `` rather than ``. – torek Aug 21 '21 at 15:59
  • 1
    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 `` 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. – torek Aug 21 '21 at 16:01
  • @torek Thank you. You should probably post this comment in Answer. It's really useful! – Wei Wong Aug 21 '21 at 16:15
  • @torek 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. [link](https://play.golang.org/p/u2UV3pGUzra) – Wei Wong Aug 21 '21 at 16:25
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/236265/discussion-between-wei-wong-and-torek). – Wei Wong Aug 21 '21 at 16:26
  • 1
    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. – torek Aug 22 '21 at 02:10
  • @torek 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. – Wei Wong Aug 23 '21 at 15:10
  • 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. – torek Aug 24 '21 at 03:01