mrj Js
mrj Js

Reputation: 23

How to assert a func to a type by reflect?

I am writing an RPC service in Go. I don't know how to convert a struct method to a Handler func.

What I tried:

type TestService struct{}

func (TestService) Foo(a int) error {
    return nil
}

type Handle func(a int) error

func TestHandle(t *testing.T) {
    ts := new(TestService)
    val := reflect.ValueOf(ts)
    // typ := reflect.TypeOf(ts)

    // fmt.Println(val.Method(0).Interface())
    // fmt.Println(val.Method(0).Type().ConvertibleTo(reflect.TypeOf(new(Handle))))

    switch reflect.Indirect(val).Method(0).Interface().(type) {
    case Handle:
        fmt.Println(" okokok " )
    default:
        fmt.Println(reflect.Indirect(val).Method(0).Type())
    }
}

But it fails. How should I do it?

Upvotes: 2

Views: 1400

Answers (1)

icza
icza

Reputation: 417592

The method TestService.Foo is of type func(a int) error, which is not the same as type Handle (Handle has the same underlying type, but it is a new, distinct type).

You have to check for this exact type:

case func(a int) error:
    fmt.Println(" okokok ")

With this change, output will be:

=== RUN   TestHandle
 okokok 
--- PASS: TestHandle (0.00s)
PASS

Try it on the Go Playground.

Note that you can do the same with a type assertion, e.g.:

if _, ok := reflect.Indirect(val).Method(0).Interface().(func(a int) error); ok {
    fmt.Println(" okokok ")
}

Try this one on the Go Playground.

Also note that if you do want to use the Handle type definition, you could check if the function value is assignable to a variable of Handle type. Using reflection, this check essentially means if the type of the method is assignable to the type of Handle.

This is how it would look like:

th := reflect.TypeOf(Handle(nil))
if reflect.Indirect(val).Method(0).Type().AssignableTo(th) {
    fmt.Println(" okokok ")
}

Try this one on the Go Playground.

Obtaining the function value

Solutions above only check if the given method is of the given function type. If you also need the function value (so you can call it), this is how you can do it:

When using type switch (Go Playground):

switch hf := reflect.Indirect(val).Method(0).Interface().(type) {
case func(a int) error:
    fmt.Println(" okokok ", hf(0))
default:
    fmt.Println(reflect.Indirect(val).Method(0).Type())
}

When using type assertion (Go Playground):

if hf, ok := reflect.Indirect(val).Method(0).Interface().(func(a int) error); ok {
    fmt.Println(" okokok ", hf(0))
}

Using Value.Convert() (Go Playground):

m := reflect.Indirect(val).Method(0)
th := reflect.TypeOf(Handle(nil))
if m.Type().AssignableTo(th) {
    var hf Handle = m.Convert(th).Interface().(Handle)
    fmt.Println(" okokok ", hf(0))
}

Upvotes: 3

Related Questions