Kurt Peek
Kurt Peek

Reputation: 57451

How to check that a return value of a function satisfies the error interface

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?

Upvotes: 2

Views: 1243

Answers (3)

mkopriva
mkopriva

Reputation: 38213

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

Upvotes: 4

colm.anseo
colm.anseo

Reputation: 22037

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

Upvotes: 2

Burak Serdar
Burak Serdar

Reputation: 51567

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

f.Out(f.NumOut() - 1).Implements(reflect.TypeOf((*error)(nil)).Elem())

Upvotes: 2

Related Questions