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

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

    // 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{}
}

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?

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 &bar{}
}

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