【问题标题】:Why does the compiler require such a strict match for function signatures?为什么编译器需要对函数签名进行如此严格的匹配?
【发布时间】:2012-10-27 22:54:21
【问题描述】:

将函数赋值给变量时,为什么编译器需要完美的函数签名匹配时...

  • 变量的类型是一个函数,其参数或返回是一个特定的接口,并且
  • 被分配的函数需要一个不同的接口,但它是一个嵌入了预期接口的接口。

以这个例子为例......

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

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

    // 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 is a Fooer
var fmake2 FMaker = func() FooerBarer {
    return &bar{}
}

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

编译器似乎会看到,通过返回 FooerBarer,您因此返回了 Fooer,并且会接受分配。

所以...

  • 编译器这种严格行为的原因是什么?
  • 正在解决什么问题或正在避免什么危险?
  • 为什么这与编译器在分配给Fooer 变量时接受FooerBarer 值有什么不同?

【问题讨论】:

  • 许多人会争辩“严格的接口是好的”。这就是编程强类型语言的全部意义,不是吗?我猜其他人都用 Perl 或 Javascript 编写代码;)
  • @paulsm4:我当然不同意那个(我还没有足够的知识不同意;-))。但在接口方面,有那种“只要你能做 X、Y 和 Z,我们就很酷” 之类的氛围,我本来希望编译器会实现。跨度>

标签: interface go


【解决方案1】:

简单地说,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 &bar{}
}

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

【讨论】:

  • 感谢您的回答。我试图正确理解你的第一段。你是说它与具有可预测的内存布局有关,它在运行时提供最快的查找?如果不是为了这样的好处,我不明白为什么会有所作为,因为编译器至少可以保证返回的值至少具有Fooer 所需的方法。
  • 是的,对于任何给定的接口,itable 的顺序都是一样的。所以如果你想要 Foo(),它是 itable 的第一个方法,并且与接口头中指向的数据有一定的偏移量。在 FooerBarer 中,第一个可能是 Bar(),第二个可能是 Foo()。
  • 好的,再次感谢。这对我来说大部分是陌生的概念。你有没有机会推荐阅读描述 Go 或一般情况下这些底层实现细节的阅读?还是只能通过阅读源代码才能收集到的东西?
  • "保证 Fooer 在 itable 中的第一个方法是 Foo() Fooer。"保证???也许只能保证 BS。规范中没有提到“itable”。
  • 您说“在 Go 中,没有分配具有自动转换”。然而,这种情况并非如此。您可以将 FooerBarer 分配给 Fooer 而无需(句法)强制转换。但是,您不能将 func() FooerBarer 分配给 func() Fooer
猜你喜欢
  • 2018-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-10
  • 1970-01-01
  • 2014-01-18
  • 2019-01-20
  • 1970-01-01
相关资源
最近更新 更多