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

◇◆丶佛笑我妖孽 提交于 2020-01-03 13:59:58

问题


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


回答1:


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()}



回答2:


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.




回答3:


I don't think this is possible. From what I can see in reflect's documentation and code, there is no way to know, whether a method is defined on the type or promoted. Seems like panic-recover is the best you can do here.




回答4:


There are 3 questions here.

  1. An embedded interface does not mean "implements A". It's exactly the same as embedding any other type of object. If you want to implement A, just make a method: func (b B) Foo() string.

    When you say:

    using embedded interfaces in structs is great for when you only want to implement /part/ of an interface

    That does work, but you have to make sure to create the object properly. Think of it like wrapping an existing object:

    type MyReadCloser struct {
        io.ReadCloser
    }
    
    func (mrc *MyReadCloser) Read(p []byte) (int64, error) {
        // do your custom read logic here
    }
    
    // you get `Close` for free
    
    func main() {
        // assuming we have some reader
        var rc io.ReadCloser
        // you have to build the object like this:
        myReader := MyReadCloser{rc}
    }
    

    I'm not sure how Go does it internally, but conceptually it's as if it creates a Close method for you:

    func (mrc *MyReadCloser) Close() error {
        return mrc.ReadCloser.Close()
    }
    
  2. The panic is because A is nil. If you had:

    type concrete string
    func (c concrete) Foo() string {
        return string(c)
    }
    func main() {
        b := B{A: c("test")}
        // etc...
    }
    

    It would work. In other words when you call:

    bMeth.Func.Call([]reflect.Value{reflect.ValueOf(B{})})
    

    That's:

    B{}.Foo()
    

    Which is:

    B{}.A.Foo()
    

    And A is nil so you get a panic.

  3. As to the question about how to get only the methods directly implemented by an object (not methods implemented by an embedded field), I wasn't able to see a way using the reflect library. MethodByName gives no indication:

    <func(main.B) string Value>
    

    Internally that's basically a function like this:

    func(b B) string {
        return b.A.Foo()
    }
    

    And I don't think there's anything in reflect that allows you to peer into the internals of a function. I tried looping over the fields, grabbing their methods and comparing the two, but that doesn't work either.



来源:https://stackoverflow.com/questions/29988632/go-reflection-with-interface-embedded-in-struct-how-to-detect-real-functions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!