2

I'd like to write some code which inspects the methods of a struct and makes certain assertions on them, for example, that the last thing returned by them should be an error. I've tried the following example script:

import (
    "context"
    "reflect"
)

type Service struct {
    name string
}

func (svc *Service) Handle(ctx context.Context) (string, error) {
    return svc.name, nil
}

func main() {
    s := &Service{}
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumMethod(); i++ {
        f := t.Method(i).Func.Type()

        f.Out(f.NumOut() - 1).Implements(reflect.TypeOf(error))
    }
}

However, this yields a

./main.go:23:51: type error is not an expression

What does compile are instead the following two lines at the end:

    var err error
    f.Out(f.NumOut() - 1).Implements(reflect.TypeOf(err))

However, this yields a panic:

panic: reflect: nil type passed to Type.Implements

What would be the correct way to check that the last arguments implements the error interface? In other words, how do I get a reflect.Type of an error interface?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Kurt Peek
  • 52,165
  • 91
  • 301
  • 526
  • 1
    You probably want to determine if the last result has type `error`, not that it implements `error`. If so, use `f.Out(f.NumOut() - 1) == reflect.TypeOf((*error)(nil)).Elem()`. – Charlie Tumahai Mar 03 '20 at 06:47
  • 1
    Possible duplicate: [Golang TypeOf without an instance and passing result to a func](https://stackoverflow.com/questions/48939416/golang-typeof-without-an-instance-and-passing-result-to-a-func/48944430#48944430). – icza Mar 03 '20 at 07:08

3 Answers3

4

If the last return value "should be" and error do not use Implements, that's not sufficient, x implements e is not the same as x is e.

Just check the type's name and package path. For predeclared types, including error, the package path is an empty string.


A non-error type that implements error.

type Service struct {
    name string
}

type sometype struct {}

func (sometype) Error() string { return "" }

func (svc *Service) Handle(ctx context.Context) (string, sometype) {
    return svc.name, sometype{}
}

func main() {
    s := &Service{}
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumMethod(); i++ {
        f := t.Method(i).Func.Type()
        rt := f.Out(f.NumOut() - 1)
        fmt.Printf("implements error? %t\n", rt.Implements(reflect.TypeOf((*error)(nil)).Elem()))
        fmt.Printf("is error? %t\n", rt.Name() == "error" && rt.PkgPath() == "")
    }
}

This outputs:

implements error? true
is error? false

A locally declared type named error that doesn't implement the builtin error.

type Service struct {
    name string
}

type error interface { Abc() }

func (svc *Service) Handle(ctx context.Context) (string, error) {
    return svc.name, nil
}

type builtin_error interface { Error() string }

func main() {
    s := &Service{}
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumMethod(); i++ {
        f := t.Method(i).Func.Type()
        rt := f.Out(f.NumOut() - 1)
        fmt.Printf("implements error? %t\n", rt.Implements(reflect.TypeOf((*builtin_error)(nil)).Elem()))
        fmt.Printf("is error? %t\n", rt.Name() == "error" && rt.PkgPath() == "")
    }
}

This outputs:

implements error? false
is error? false

The actual builtin error.

type Service struct {
    name string
}

func (svc *Service) Handle(ctx context.Context) (string, error) {
    return svc.name, nil
}

func main() {
    s := &Service{}
    t := reflect.TypeOf(s)

    for i := 0; i < t.NumMethod(); i++ {
        f := t.Method(i).Func.Type()
        rt := f.Out(f.NumOut() - 1)
        fmt.Printf("implements error? %t\n", rt.Implements(reflect.TypeOf((*error)(nil)).Elem()))
        fmt.Printf("is error? %t\n", rt.Name() == "error" && rt.PkgPath() == "")
    }
}

This outputs:

implements error? true
is error? true
mkopriva
  • 35,176
  • 4
  • 57
  • 71
2

Use a pointer to interface, and get the Elem of it, like this:

f.Out(f.NumOut() - 1).Implements(reflect.TypeOf((*error)(nil)).Elem())
Burak Serdar
  • 46,455
  • 3
  • 40
  • 59
2

To get the reflect.TypeOf of an error without using an existing error, you can use this one-liner:

reflect.TypeOf((*error)(nil)).Elem()

Basically, it first gets type of a pointer to error (*error) and then Elem() "deferences" the TypeOf to the type of error.

Playground

colm.anseo
  • 19,337
  • 4
  • 43
  • 52