Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go reflection with interface embedded in struct - how to detect "real" functions?

The situation I have now is the same as was asked about in this thread: Meaning of a struct with embedded anonymous interface?

  type A interface {
     Foo() string
  }

  type B struct {
     A
     bar string
  }

Idiomatically, coming from a backround in OOP languages, what it looks like this pattern is "trying to say" to me is that B must implement interface A. But I get by now that "Go is different". So, rather than the compile-time check I expected at first, this is happy to compile with or without a

  func (B) Foo() string { .... }

present. As the above question points out (paraphrased): "using embedded interfaces in structs is great for when you only want to implement /part/ of an interface".

Presumably, this is because what is happening with this embed is just like in every other case - a value of type B would have an anonymous interface value of type A, as a field. Personally while I find that orthogonality comforting, I also find it confusing that the reflection package would then let me get methods of A directly from B's type this way, and not error/nil if no method with receiver B is present. But - this question isn't about the thinking behind that - it is about how that interface value is initialized after b := B{}:

 func main() {
    bType := reflect.TypeOf(B{})
    bMeth, has := bType.MethodByName("Foo")
    if has {
      fmt.Printf("HAS IT: %s\n",bMeth.Type.Kind())
      res := bMeth.Func.Call([]reflect.Value{reflect.ValueOf(B{})})
      val := res[0].Interface()
      fmt.Println(val)
  } else {
      fmt.Println("DOESNT HAS IT")
  }
}

When this is run, it causes a horrible panic

 HAS IT: func
 panic: runtime error: invalid memory address or nil pointer dereference

... or doesn't - depending on if the compiler/runtime was able to find the above method. So: How can I detect that situation before I trigger it?

That is - is there something about the bMeth value I can use to see that there is no "real" implementation present in the reflection-returned returned Method and func values? Is that more precisely something like "is the pointer to the function in the function table of the anonymous interface value in zero", or what exactly is going on with methods you pull from an interface with reflection where there is no implementation?

Wrapping the whole thing in a goroutine and attempting to run the function under defer/panic isn't the answer - not only because of the expense of the panic/defer but because the function in general might, if it does exist, have side effects I don't want right now...

Do I want something like a run-time implementation that mirrors the compiler's type check? Or is there an easier way? Am I thinking about this incorrectly?

Above example in a Go playground

like image 273
BadZen Avatar asked May 01 '15 14:05

BadZen


3 Answers

You needn't reflection to my mind

method_in_table := B.Foo
fmt.Printf("%T \n", method_in_table)

will output you

func(main.B) string

Interface type A initialized at predeclared nil which has no dynamic type

var a A
if a==nil{
    fmt.Printf("It's nil")
}
a.Foo()

will give you same error. So practical check can be just

if b.A != nil { b.Foo()}
like image 145
Uvelichitel Avatar answered Nov 12 '22 12:11

Uvelichitel


This question is old with some good answers, but none presents the possibility that this can be done.

Before presenting the solution: I think it's not your job to make sure the implementation does not panic because it fails to set an embedded interface field. Someone could pass an implementation which explicitly defines the methods in which panic() is called explicitly. You could not detect that case, yet, that implementation wouldn't be any better than a nil embedded interface field.

OK, so how to tell if a method cannot be called because it would panic due to the implementation not being available because the embedded interface field is nil?

You said you can't / don't want to call the method and recover from a panic because if the method is available, this would call it and have its side effect.

The fact is that we don't have to call it. We can just refer to the method via an instance (not type), and then the actual receiver has to be resolved. Of course if the receiver would be the dynamic value of an embedded interface, and if that interface is nil, the resolving will cause a runtime panic, but the method will not be called even if the embedded interface is not nil. Note that this is in fact a Method value, and obtaining a method value evaluates and saves the receiver. This receiver evaluation is what will fail.

Let's see an example:

type A interface {
    Foo() string
}

type B struct {
    A
}

func (b B) Int() int {
    fmt.Println("B.Int() called")
    return 0
}

func main() {
    b := B{}
    _ = b.Int
    fmt.Println("We got this far, b.Int is realized")
}

What will this program output? Only "We got this far, b.Int is realized". Because the Int() method is explicitly defined for the B type, and so b.Int can be resolved. And since it's not called, "B.Int() called" will not be printed.

What if we do this:

_ = b.Foo

Since Foo is a promoted method from B.A embedded interface, and b.A is nil, resolving b.Foo will fail at runtime, and produce a runtime error, something like this:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47d382]

goroutine 1 [running]:
main.main()
    /tmp/sandbox877757882/prog.go:24 +0x2

But we can recover from this:

defer func() {
    if r := recover(); r != nil {
        fmt.Println("Recovered:", r)
        fmt.Println("This means b.Foo is not realized!")
    }
}()
_ = b.Foo

This will output:

Recovered: runtime error: invalid memory address or nil pointer dereference
This means b.Foo is not realized!

Try the examples on the Go Playground.

like image 21
icza Avatar answered Nov 12 '22 12:11

icza


Let me put my two cents in, after you've already received good answers for your question.

Presumably, this is because what is happening with this embed is just like in every other case - a value of type B would have an anonymous interface value of type A, as a field.

You've basically solved the problem here. This is just a field, but because it's anonymous all its methods are being promoted and you can use them directly on the struct. This is not only related to interfaces, but the problem you've pointed to exists within ordinary structures as well:

package main

type A struct {
}

func (a A) Foo() {
}

type B struct {
    *A
}

func main() {
    B{}.Foo()
}

This will cause panic. I believe this is expected: we're saying B embeds *A, but then leave it uninitialised, so what am I thinking? We could try to find an analogy here with, for example, C++ and find out it is similar to a null pointer in C++ – how do we deal with it there? We either expect it to be non-null (by a contract) or need to check before using. The latter it what Uvelichitel suggested in the accepted answer and it's by no means correct and there is no better solution I think. Although it's not very plausible. We do expect the caller to know the method they're calling is a promoted method of an anonymous field which is a pointer (or interface) type and as such can be nil. As an author of such code I would either need to make sure it's never nil (contract) or state it clearly in documentation that a caller needs to check it (but why would I embed this type then instead of having normal field, I'm not sure).

It bothers me with interfaces though, because looking back at your example and making A an interface, we have a following problem:

package main

import "fmt"

type A interface {
    Foo()
}

type B struct {
    A
}

func main() {
    var b interface{}
    b = &B{}

    // Nicely check whether interface is implemented
    if a, ok := b.(A); ok {
        a.Foo()
    }
}

Whoops, panic. I explicitly don't use reflect package here to indicate your problem exists within "normal" language usage. I have an interface object b and want to check whether it implements interface A. The answer is yes, but I'm getting panic. Who is to blame? I would feel much more comforting saying the creator of object behind the interface b who advertise some functionality, but don't care to provide the implementation. As such I would like it to call a bad practice or at least force it to be clearly stated in the documentation rather than assuming ok in the above type assertion means actually ok.

It's getting too long and off topic I think. My answer to your question is then a mixture of already given answers: directly check A is not null and if it's not possible (you don't know the exact field promoting the method), hope for the best and blame someone else.

like image 2
tomasz Avatar answered Nov 12 '22 13:11

tomasz