Golang: how to determine a method is promoted from an embedded struct instead of being directly implemented

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

Golang: how to determine a method is promoted from an embedded struct instead of being directly implemented

问题

如何判断一个方法/接口是否是从嵌入的结构体中提升而来(换句话说,一个结构体“继承”了一个方法而不是直接实现它)?

例如:有没有办法确定在下面的情况下,Main 并没有直接实现接口 Interface

type Interface interface {
  Method()
}

type Embedded struct{}
func (t *Embedded) Method() {}

type Main struct {
  Embedded
}

...与下面的情况不同,其中 Main 直接实现了接口:

type Main struct {
  Embedded
}
func (t *Main) Method() {} // 这是直接实现的

尝试过的方法:

go 中,标准类型断言的结果是 Main 在两个例子中都具有 Method,符合预期,即实现了 Interface

main := &Main{}
_, implementsInterface := main.(Interface) // implementsInterface 为 true

反射也表明在两个例子中,Main 的实例都具有该方法:

main := &Main{}

mainVal := reflect.ValueOf(main)
mainMethod, hasMainMethod := mainVal.Type().MethodByName("Method") // hasMainMethod 为 true

embedded := val.Elem().Field(0)
embeddedMethod, hasEmbeddedMethod := embedded.Type().MethodByName("Method") // hasEmbeddedMethod 为 true

// mainMethod != embeddedMethod
// 方法中的指针不相同
// 是否有其他方法可以确定这些方法是“相同”的?
英文:

How can I tell if a method/interface is promoted from an embedded struct (in other words: a struct "inherits" a method instead of directly implementing it)?

For example: is there a way to determine Main does not directly implement the interface Interface given this?:

type Interface interface {
  Method()
}

type Embedded struct{}
func (t *Embedded) Method() {}

type Main struct {
  Embedded
}

... as opposed to the case where it does directly implement the interface:

type Main struct {
  Embedded
}
func (t *Main) Method() {} // this is directly implemented

Tried:

Standard type assertion in go results in Main having the Method in both examples, implementing Interface, as expected:

main := &Main{}
_, implementsInterface := main.(Interface) // implementsInterface is true

Reflection also indicates an instance of Main has the method in both examples:

main := &Main{}

mainVal := reflect.ValueOf(main)
mainMethod, hasMainMethod := mainVal.Type().MethodByName("Method") // hasMainMethod is true

embedded := val.Elem().Field(0)
embeddedMethod, hasEmbeddedMethod := embedded.Type().MethodByName("Method") // hasEmbeddedMethod is true

// mainMethod != embeddedMethod
// pointers in the methods are not the same
// is there some other way to determine these methods are "the same"?

答案1

得分: 2

编译器将为提升的方法创建一个包装器。因此,首先,提升的方法与原始方法是不同的。

另一方面,这个事实给我们提供了一个不太可靠的解决方法:如果一个方法是一个包装器,它可能是一个提升的方法。而且,检查一个方法是否是编译器生成的包装器的一个不太可靠的方法是检查与该方法对应的源代码的文件名。对于一个包装器,文件名将是<autogenerated>

让我们先看一个演示:

package main

import (
	"fmt"
	"reflect"
	"runtime"
)

type inner struct{}

func (t *inner) Method() {}

type outer1 struct{ inner }

type outer2 struct{ inner }

func (o *outer2) Method() {}

func isPromoted(s any, methodName string) (bool, error) {
	v := reflect.ValueOf(s)
	m, ok := v.Type().MethodByName(methodName)
	if !ok {
		return false, fmt.Errorf("method sets of s does not include the method")
	}

	p := m.Func.Pointer()
	f := runtime.FuncForPC(p)

	fileName, _ := f.FileLine(f.Entry())

	promoted := fileName == "<autogenerated>"
	return promoted, nil
}

func main() {
	i := &inner{}
	o1 := &outer1{inner: *i}
	o2 := &outer2{inner: *i}

	fmt.Println("i - is [Method] promoted?")
	fmt.Println(isPromoted(i, "Method"))

	fmt.Println("o1 - is [Method] promoted?")
	fmt.Println(isPromoted(o1, "Method"))

	fmt.Println("o2 - is [Method] promoted?")
	fmt.Println(isPromoted(o2, "Method"))
}

输出结果(参见https://go.dev/play/p/XUIE0ws3eFN):

i - is [Method] promoted?
false <nil>
o1 - is [Method] promoted?
true <nil>
o2 - is [Method] promoted?
false <nil>

这个解决方法不太可靠,因为:

  1. 包装器不一定是提升的方法。例如,当将i.Method作为参数传递给函数时,会创建一个包装器(请参阅上面的链接以查看演示)。我不确定是否还有其他情况。

  2. 演示中使用了(v Value).Pointer。根据文档

    如果v的Kind是Func,返回的指针是一个底层代码指针,但不一定足以唯一标识一个函数。

    还有实现中的注释

    如文档注释所说,返回的指针是一个底层代码指针,但不一定足以唯一标识一个函数。通过reflect创建的所有方法表达式都具有相同的底层代码指针,因此它们的指针相等。

    isPromoted在演示中对通过reflect创建的方法有效吗?这还没有测试。

  3. <autogenerated>是一个实现细节,没有记录在文档中。它可能随时更改。

英文:

The compiler will create a wrapper for a promoted method. So in the first place, the promoted method is different from the original method.

On the other hand, this fact gives us a not-so-reliable workaround: if a method is a wrapper, it could be a promoted method. And a still not-so-reliable way to check if a method is a compiler-generated wrapper is to check the file name of the source code corresponding to the method. For a wrapper, the file name will be &lt;autogenerated&gt;.

Let's see a demo first:

package main

import (
	&quot;fmt&quot;
	&quot;reflect&quot;
	&quot;runtime&quot;
)

type inner struct{}

func (t *inner) Method() {}

type outer1 struct{ inner }

type outer2 struct{ inner }

func (o *outer2) Method() {}

func isPromoted(s any, methodName string) (bool, error) {
	v := reflect.ValueOf(s)
	m, ok := v.Type().MethodByName(methodName)
	if !ok {
		return false, fmt.Errorf(&quot;method sets of s does not include the method&quot;)
	}

	p := m.Func.Pointer()
	f := runtime.FuncForPC(p)

	fileName, _ := f.FileLine(f.Entry())

	promoted := fileName == &quot;&lt;autogenerated&gt;&quot;
	return promoted, nil
}

func main() {
	i := &amp;inner{}
	o1 := &amp;outer1{inner: *i}
	o2 := &amp;outer2{inner: *i}

	fmt.Println(&quot;i - is [Method] promoted?&quot;)
	fmt.Println(isPromoted(i, &quot;Method&quot;))

	fmt.Println(&quot;o1 - is [Method] promoted?&quot;)
	fmt.Println(isPromoted(o1, &quot;Method&quot;))

	fmt.Println(&quot;o2 - is [Method] promoted?&quot;)
	fmt.Println(isPromoted(o2, &quot;Method&quot;))
}

And the output (see https://go.dev/play/p/XUIE0ws3eFN):

i - is [Method] promoted?
false &lt;nil&gt;
o1 - is [Method] promoted?
true &lt;nil&gt;
o2 - is [Method] promoted?
false &lt;nil&gt;

This workaround is not-so-reliable because:

  1. a wrapper is not necessary a promoted method. For example, when passing i.Method as a parameter to a function, a wrapper is created (follow the link above to the playground to see a demo). I'm not sure whether there are other cases.

  2. the demo uses (v Value).Pointer. According to the doc:
    > If v's Kind is Func, the returned pointer is an underlying code pointer, but not necessarily enough to identify a single function uniquely.

    And the comment in the implementation:
    > As the doc comment says, the returned pointer is an
    underlying code pointer but not necessarily enough to
    identify a single function uniquely. All method expressions
    created via reflect have the same underlying code pointer,
    so their Pointers are equal.

    Does isPromoted implemented in the demo work for methods created via reflect? It's not tested.

  3. &lt;autogenerated&gt; is an implementation detail and is not documented. It could be changed any time.

huangapple
  • 本文由 发表于 2023年5月13日 00:52:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76238476.html
匿名

发表评论

匿名网友

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

确定