Move on to reflection with an interface built into the structure - how to detect "real" functions?

The situation that I have now is the same as in this topic: The value of a structure with a built-in anonymous interface?

type A interface { Foo() string } type B struct { A bar string } 

Idiomatically, based on the background image in OOP languages, what looks like this template, “trying to tell” me that B should implement the interface A. But I still understand that “Go is different”. Thus, rather than checking the compilation time that I expected at the beginning, it will happily compile with or without

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

is present. As mentioned above, the question (rephrased) is: "using built-in interfaces in structures is great when you only want to implement / part / interfaces."

Presumably, this is due to the fact that what happens with this embedding is exactly the same as in any other case - a value of type B will have the value of an anonymous interface of type A as a field. Personally, when I find that orthogonality is calming, I am also confused that the reflection package will then allow me to get methods A directly from type B in this way, and not error / no if there is no method with receiver B. But, this question It’s not connected with thinking, but in how this 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 done, it causes a terrible panic

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

... or not - depending on whether the compiler / runtime could find the above method. So: How can I detect this situation before it starts?

That is - is there something about the bMeth value that I can use to see that there is no real implementation in the returned Method and func return values? More precisely, is it something like "is a pointer to a function in the table of functions of the anonymous value of the interface to zero" or what exactly happens to the methods that you extract from the interface with reflection, where there is no implementation?

Giving the whole thing to goroutine and trying to run the function during deferral / panic is not the answer - not only because of the expense of panic / deferral, but also because the function can, if it exists, have side effects that I don’t want directly now...

Do I need something like a runtime implementation that reflects compiler type checking? Or is there an easier way? Am I thinking about it wrong?

Example above on the Go Playground

+5
source share
4 answers

You do not need to speculate.

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

will bring you

 func(main.B) string 

Interface type A is initialized to a predefined zero, which does not have a dynamic type

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

will give you the same error. So a practical test can be simple

 if bA != nil { b.Foo()} 
+3
source

Let me put your two cents after you have already received good answers to your question.

Presumably, this is due to the fact that what happens with this embedding is exactly the same as in any other case - a value of type B will have the value of an anonymous interface of type A as a field.

You basically solved the problem. This is just a field, but since it is anonymous, all of its methods are promoted, and you can use them directly in the structure. This is not only related to interfaces, but the problem you mentioned also exists in ordinary structures:

 package main type A struct { } func (a A) Foo() { } type B struct { *A } func main() { B{}.Foo() } 

This will cause a panic. I believe this was expected: we say B implements *A , but then leave it uninitialized, so what I think? We could try to find an analogy here, for example, with C ++ and find out that it looks like a null pointer to C ++ - how do we deal with it? We either expect it to be non-null (by contract), or we need to check before use. The latter is what Magnifier suggested in the accepted answer, and this is by no means correct, and there is no better solution that I think. Although this is not very believable. We expect the caller to know that the method they are calling is an advanced method of the anonymous field, which is a type of pointer (or interface), and as such may be nil. As the author of such code, I would either have to make sure that it will never be anywhere (a contract), or clearly indicate it in the documentation that the caller should check (but why should I insert this type, instead of having a normal field, I don’t sure).

This bothers me with the interfaces because, looking back at your example and making an A interface, we have the 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() } } 

Oops, panic. I obviously do not use the reflection package here to indicate that your problem exists in the "normal" use of the language. I have an interface object B and want to check if it implements interface A The answer is yes, but I am panicking. Who's guilty? I would feel a much more comforting statement by the creator of the object behind interface B , which advertises some functions but does not want to provide an implementation. Therefore, I would like him to name bad practice or, at least, make it clearly indicated in the documentation, and not assume ok in the above type, this means that it is really normal.

This is getting too long, and I think it is not. My answer to your question is a mixture of the answers already provided: directly checking A not zero, and if this is not possible (you do not know the exact field conducive to the method), hope for the best and blame someone else.

+2
source

I do not think that's possible. From what I see in the documentation reflect and code , there is no way to find out if a method is defined for type or. It seems that panic-recover is the best you can do here.

+1
source

There are 3 questions here.

  • The built-in interface does not mean "implements A". This is exactly the same as embedding any other type of object. If you want to implement A, just create a method: func (b B) Foo() string .

    When you speak:

    using built-in interfaces in structures is great when you only want to implement / part / interfaces

    This works, but you must definitely create an object. Think of it as 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 this inside, but conceptually it is as if it were creating the Close method for you:

     func (mrc *MyReadCloser) Close() error { return mrc.ReadCloser.Close() } 
  • The panic is that A is nil . If you have:

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

    It will work. In other words, when you call:

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

    It:

     B{}.Foo() 

    What is:

     B{}.A.Foo() 

    And A is nil , so you get a panic.

  • Regarding the question of how to get only methods directly implemented by the object (and not methods implemented by the built-in field), I could not see the method using the reflect library. MethodByName not specified:

     <func(main.B) string Value> 

    Internally, that basically such a function:

     func(b B) string { return bAFoo() } 

    And I don’t think that there is something in reflect that allows you to look inside functions. I tried to iterate over the fields, grabbing their methods and comparing them, but that doesn't work either.

+1
source

All Articles