Go反射中嵌入在结构体中的接口 – 如何检测“真实”的函数?

huangapple go评论90阅读模式
英文:

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

问题

我现在的情况与这个帖子中提到的情况相同:https://stackoverflow.com/questions/24537443/meaning-of-a-struct-with-embedded-anonymous-interface

type A interface {
   Foo() string
}

type B struct {
   A
   bar string
}

从惯用语的角度来看,作为一名面向对象编程语言的背景,这个模式对我来说就像是在“告诉”我 B 必须实现接口 A。但我现在明白了,“Go 是不同的”。所以,与我最初期望的编译时检查不同,这段代码在有或没有下面这段代码的情况下都可以编译通过:

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

正如上面的问题指出的(改写的):“在结构体中使用嵌入接口非常适合当你只想实现接口的一部分时”。

这可能是因为在这种嵌入的情况下,与其他情况一样,类型为 B 的值会作为一个字段拥有类型为 A 的匿名接口值。个人而言,虽然我觉得这种正交性很令人安心,但我也觉得困惑的是,反射包会让我以这种方式直接从 B 的类型中获取 A 的方法,并且如果没有带有接收者 B 的方法,则不会报错/返回 nil。但是,这个问题不是关于这背后的思考,而是关于在 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")
    }
}

当运行这段代码时,会导致一个可怕的 panic:

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

...或者不会,这取决于编译器/运行时是否能够找到上述方法。所以:在触发它之前,我如何检测到这种情况?

也就是说,有关 bMeth 值的某些信息可以用来判断反射返回的 Method 和 func 值中是否存在“真正”的实现吗?更准确地说,这是否更像是“匿名接口值的函数表中的函数指针是否为零”,或者关于使用反射从接口中提取的方法的情况到底发生了什么?

将整个过程包装在一个 goroutine 中,并在 defer/panic 下尝试运行该函数并不是答案,不仅因为 panic/defer 的开销,而且因为该函数(如果存在)可能会产生我现在不想要的副作用...

我是否需要像编译器的类型检查一样的运行时实现?还是有更简单的方法?我对此的思考是否正确?

在 Go playground 中的上述示例

英文:

The situation I have now is the same as was asked about in this thread: https://stackoverflow.com/questions/24537443/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

得分: 3

你不需要对我的想法进行反思

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

将输出

func(main.B) string

接口类型A初始化为预声明的nil,它没有动态类型

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

将会给你同样的错误。因此,实际的检查可以只是

if b.A != nil { b.Foo()}

英文:

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

得分: 3

这个问题是旧的,有一些很好的答案,但没有一个提出了这个可以实现的可能性。

在提出解决方案之前:我认为你的工作不是确保实现不会因为无法设置嵌入接口字段而引发 panic。有人可以传递一个明确定义了调用 panic() 的方法的实现。你无法检测到这种情况,然而,那个实现与 nil 嵌入接口字段一样糟糕。

好的,那么如何判断一个方法是否不能被调用,因为它会因为实现不可用而引发 panic,而嵌入接口字段是 nil

你说你不能/不想调用该方法并从 panic 中恢复,因为如果该方法可用,这将调用它并产生副作用。

事实上,我们不必调用它。我们只需通过实例(而不是类型)引用该方法,然后必须解析实际的接收器。当然,如果接收器是嵌入接口的动态值,并且该接口是 nil,解析将导致运行时 panic,但即使嵌入接口不是 nil,该方法也不会被调用。请注意,这实际上是一个方法值,获取方法值会评估并保存接收器。这个接收器的评估就是会失败的。

让我们看一个例子:

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

这个程序会输出什么?只有 "We got this far, b.Int is realized"。因为 Int() 方法是显式为 B 类型定义的,所以可以解析 b.Int。由于它没有被调用,"B.Int() called" 不会被打印出来。

如果我们这样做:

_ = b.Foo

由于 Foo 是从 B.A 嵌入接口继承的方法,并且 b.Anil,解析 b.Foo 将在运行时失败,并产生一个运行时错误,类似于这样:

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

但我们可以从中恢复:

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

这将输出:

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

Go Playground上尝试这些例子。

英文:

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.

答案3

得分: 2

让我来发表一下我的意见,在你已经得到了关于你问题的好答案之后。

你已经基本解决了这个问题。这只是一个字段,但由于它是匿名的,它的所有方法都被提升了,你可以直接在结构体上使用它们。这不仅与接口有关,你指出的问题也存在于普通的结构体中:

package main

type A struct {
}

func (a A) Foo() {
}

type B struct {
	*A
}

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

这会导致恐慌。我认为这是可以预料的:我们说 B 嵌入了 *A,但是没有对其进行初始化,那我在想什么呢?我们可以尝试在这里找到一个类似的比喻,比如在 C++ 中,它类似于 C++ 中的空指针——我们如何处理它?我们要么期望它不为空(通过合同),要么在使用之前需要进行检查。后者是 Uvelichitel 在接受的答案中提到的,这绝对是正确的,我认为没有更好的解决办法。虽然这不太可能发生。我们期望调用者知道他们调用的方法是一个匿名字段的提升方法,它是一个指针(或接口)类型,因此可能为空。作为这样代码的作者,我要么需要确保它永远不为空(合同),要么在文档中明确说明调用者需要检查它(但是如果我不确定为什么要嵌入这种类型而不是拥有普通字段,我不确定)。

不过,这在接口中让我感到困扰,因为回顾一下你的例子,将 A 设为接口,我们有以下问题:

package main

import "fmt"

type A interface {
	Foo()
}

type B struct {
	A
}

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

	// 检查接口是否实现
	if a, ok := b.(A); ok {
		a.Foo()
	}
}

哎呀,恐慌。我在这里明确地不使用反射包,以表明你的问题存在于“正常”的语言使用中。我有一个接口对象 b,想要检查它是否实现了接口 A。答案是肯定的,但我却得到了恐慌。谁应该受到指责?我更愿意说是接口 b 背后的对象的创建者,他们宣传了一些功能,但不关心提供实现。因此,我希望将其称为一种不良实践,或者至少强制在文档中明确说明,而不是假设上述类型断言中的 ok 实际上是“ok”。

我觉得这个回答已经太长并且离题了。对于你的问题,我的答案是已经给出的答案的混合:直接检查 A 不为空,如果不可能(你不知道确切的字段提升方法),那就希望一切顺利,并责怪别人。

英文:

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.

答案4

得分: 1

我不认为这是可能的。根据我在reflect的文档和代码中所看到的,没有办法知道一个方法是在类型上定义的还是提升的。似乎在这里,最好的方法就是使用panic-recover。

英文:

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.

答案5

得分: 1

这里有3个问题。

  1. 嵌入式接口并不意味着"实现A"。它与嵌入任何其他类型的对象完全相同。如果你想要实现A,只需创建一个方法:func (b B) Foo() string

    当你说:

    在结构体中使用嵌入式接口非常适合当你只想要实现接口的一部分时

    这是可行的,但你必须确保正确创建对象。可以将其视为包装现有对象:

     type MyReadCloser struct {
         io.ReadCloser
     }
    
     func (mrc *MyReadCloser) Read(p []byte) (int64, error) {
         // 在这里执行自定义读取逻辑
     }
    
     // 你可以免费获得`Close`方法
    
     func main() {
         // 假设我们有一些读取器
         var rc io.ReadCloser
         // 你必须像这样构建对象:
         myReader := MyReadCloser{rc}
     }
    

    我不确定Go在内部是如何实现的,但在概念上,它就像为你创建了一个Close方法:

     func (mrc *MyReadCloser) Close() error {
         return mrc.ReadCloser.Close()
     }
    
  2. 发生panic是因为Anil。如果你有:

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

    这样是可以工作的。换句话说,当你调用:

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

    这相当于:

     B{}.Foo()
    

    即:

     B{}.A.Foo()
    

    Anil,所以会发生panic。

  3. 关于如何仅获取对象直接实现的方法(而不是由嵌入字段实现的方法)的问题,我无法在reflect库中找到方法。MethodByName没有提供任何指示:

     <func(main.B) string Value>
    

    在内部,它基本上是这样一个函数:

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

    我不认为reflect中有任何允许你查看函数内部的内容的方法。我尝试循环遍历字段,获取它们的方法并进行比较,但也不起作用。

英文:

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(&quot;test&quot;)}
         // 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:

     &lt;func(main.B) string Value&gt;
    

    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.

huangapple
  • 本文由 发表于 2015年5月1日 22:20:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/29988632.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定