为什么编译器对函数签名要求如此严格的匹配?

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

Why does the compiler require such a strict match for function signatures?

问题

当将一个函数赋值给一个变量时,为什么编译器要求函数签名完全匹配,即使变量的类型是一个参数或返回值为特定接口的函数,并且被赋值的函数需要一个不同的接口,但是这个接口嵌入了期望的接口。

以这个例子为例...

  • Fooer 是一个接口
  • FooerBarer 是一个嵌入了 Fooer 接口的接口
  • *bar 实现了 FooerBarer

http://play.golang.org/p/8NyTipiQak

// 定义一个返回 Fooer 接口的函数类型
type FMaker func() Fooer

/* 定义 FMaker 类型的值 */

// 这个可以工作,因为签名与 FMaker 类型匹配
var fmake FMaker = func() Fooer {
	return &bar{}
}

// 这个会导致错误,即使 FooerBarer 是一个 Fooer
var fmake2 FMaker = func() FooerBarer {
	return &bar{}
}

所以我的问题不是关于替代解决方案,而是为什么编译器是这样构建的。

看起来编译器应该看到通过返回一个 FooerBarer,你因此返回了一个 Fooer,并且应该接受这个赋值。

那么...

  • 这种严格行为的原因是什么?
  • 解决了什么问题或避免了什么危险?
  • 为什么这与编译器接受将 FooerBarer 值赋给 Fooer 变量的赋值不同?
英文:

When assigning a function to a variable, why does the compiler require a perfect function signature match when...

  • The variable's type is a function whose parameter or return is a specific interface, and
  • The function being assigned requires a different interface, but is an interface that embeds the expected interface.

Take this example where...

  • Fooer is an interface
  • FooerBarer is an interface that embeds the Fooer interface
  • *bar implements FooerBarer

http://play.golang.org/p/8NyTipiQak

<!-- language:go -->

<pre><code> // Define a type that is a function that returns a Fooer interface
type FMaker func() Fooer

/* Define values of the FMaker type */

// This works, because the signature matches the FMaker type

var fmake FMaker = func() Fooer {
return &bar{}
}

// This causes an error even though a FooerBarer &lt;b&gt;&lt;i&gt;is&lt;/i&gt;&lt;/b&gt; a Fooer

var fmake2 FMaker = func() FooerBarer {
return &bar{}
}
</code></pre>

So my question is not about an alternate solution, but rather why the compiler is built this way.

It would seem that the compiler would see that by returning a FooerBarer, you are therefore returning a Fooer, and would accept the assignment.

So...

  • What is the reason for this strict behavior of the compiler?
  • What problem is being solved or danger is being avoided?
  • Why is this any different than the compiler accepting a FooerBarer value in an assignment to a Fooer variable?

答案1

得分: 3

简单来说,Fooer不是FooerBarer。它们都是接口类型,但它们指向不同的itable。Fooer保证在itable中的第一个方法是Foo() Fooer。而在FooerBarer中,它的第一个方法可能是Bar() FooerBarer。因此,在运行时,方法查找可能会返回错误的方法。

将FooerBarer转换为Fooer是肯定会成功的,因为FooerBarer始终具有Fooer所需的方法集。接口转换的工作方式是,运行时首先查找接收到的FooerBarer的真实类型(例如bar),然后查找bar/Fooer对应的itable,并创建一个新的接口值。

在Go代码中,你可以显式或隐式地引发这种情况。例如x := Fooer(myFooerBarer)。这将进行显式转换,并将新的接口值放入x中。如果你有一个类型为func(Fooer)的函数,并传递了一个FooerBarer,那么转换将隐式发生。编译器将执行转换,并将结果分配给函数调用的参数。

在你上面的例子中,你试图将func() FooerBarer赋值给func() Fooer。在Go中,没有自动转换的赋值。你不能将double赋值给int。即使它们的底层类型相同,你也不能将time.Duration赋值给int64。在这种情况下,函数需要被包装,以便每次运行函数时都可以进行转换。不允许自动转换相同底层类型之间的转换,并自动包装函数会有点不一致。

如果你真的需要做这样的事情,有一个简单的解决方法。只需包装函数。

var fbmake = func() FooerBarer {
    return &amp;bar{}
}

var fmake Fmaker = func() Fooer {
    return fbmake()
}
英文:

To put it simply, a Fooer is not a FooerBarer. Both are interface types, but they point to different itables. A Fooer is guaranteed to have the first method in the itable be Foo() Fooer. In a FooerBarer, it may have Bar() FooerBarer as its first method. So during runtime a method lookup would return the wrong method.

Any conversion from a FooerBarer to a Fooer is guaranteed to succeed because a FooerBarer always has the method set required for a Fooer. The way interface conversion works, the runtime first looks up the real type of the FooerBarer it has received (such as a bar) and then looks up the itable for the bar/Fooer pair and creates a new interface value.

In Go code, you can cause this to happen explicitly or implicitly. For example x := Fooer(myFooerBarer). This would do an explicit conversion and place the new interface value in x. If you had a function of type func(Fooer) and passed a FooerBarer, then the conversion would happen implicitly. The compiler would do the conversion and assign the result to the parameter of the function call.

In your case above, you are attempting to assign a func() FooerBarer to a func() Fooer. In Go, no assignment has an automatic conversion. You can not assign a double to an int. You cannot even assign a time.Duration to an int64 even though their underlying types are identical. In this case, the function would need to be wrapped so that the conversion could be done each time the function was run. Not allowing conversions between the same underlying type to be automatic and automatically wrapping functions would be a bit inconsistent.

If you really need to do something like this, there is an easy answer. Just wrap the function.

var fbmake = func() FooerBarer {
    return &amp;bar{}
}

var fmake Fmaker = func() Fooer {
    return fbmake()
}

huangapple
  • 本文由 发表于 2012年10月28日 06:54:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/13104901.html
匿名

发表评论

匿名网友

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

确定