Is this a bug?

157 views
Skip to first unread message

Henry

unread,
May 24, 2019, 12:55:06 PM5/24/19
to golang-nuts
Hi, 

I stumbled across this weird behavior and I wonder whether this is a bug. Here is the simplified version of the problem (Playground link https://play.golang.org/p/mch6NQdTpr5):

package main

import (
"fmt"
)

func main() {
str := MyString("World")
var formatter Format
Print(formatter, str) //ERROR: have Format(StringerA) string, want Format(StringerB) string
}

//StringerA and StringerB are identical. Some may wonder why declare identical interfaces in the same package.
//In a real world example, they live in different packages. StringerA resides in Package A and StringerB is in Package B. 

type StringerA interface {
String() string
}

type StringerB interface {
String() string
}


//MyString is the concrete implementation of Stringer. It applies to both StringerA and StringerB.

type MyString string

func (m MyString) String() string {
return string(m)
}


//Then, we have the Formatter. FormatterA and FormatterB are supposed to be identical. They both accept a Stringer. 
//FormatterA accepts StringerA, because they are in the same package. FormatterB accepts StringerB. Package A and Package B
//are probably authored by different people and they define their own Stringer. However, since we are dealing with interfaces, 
//so behaviour-wise, they are supposed to be identical. They format a Stringer.

type FormatterA interface {
Format(StringerA) string
}

type FormatterB interface {
Format(StringerB) string
}


//Format is the concrete implementation of Formatter. In this case, Format is implemented by the author of Package A. Let's call him Author A.
//Hence, we see StringerA there because StringerA is Author A definition of Stringer. However, it is intended to work on any Stringer.

type Format struct{}

func (f Format) Format(s StringerA) string {
return fmt.Sprintf("Hello %s!", s)
}


//Print is written by Author B. She uses FormatterB and StringerB. Those interfaces are the 'contracts' she requires for her function to work. 
//Her intention is for Print to work with any Formatter and any Stringer. However, Go apparently treat FormatterA and FormatterB as being different.
//Is this a bug?

func Print(f FormatterB, s StringerB) {
fmt.Println(f.Format(s))
}


Thanks


Henry

Wagner Riffel

unread,
May 24, 2019, 2:54:11 PM5/24/19
to Henry, golang-nuts
It's not a bug, FormatterA and FormatterB method has different
signatures, they are not identical, one wants Format(StringerA) other
Format(StringerB), thus your Format type only implements FormatterA
but Print wants a FormatterB.

Burak Serdar

unread,
May 24, 2019, 2:58:13 PM5/24/19
to Henry, golang-nuts
On Fri, May 24, 2019 at 10:55 AM Henry <henry.ad...@gmail.com> wrote:
>
> Hi,
>
> I stumbled across this weird behavior and I wonder whether this is a bug. Here is the simplified version of the problem (Playground link https://play.golang.org/p/mch6NQdTpr5):

I believe this is working as intended because a type implements an
interface if it has the same method signatures as that interface, and
Format(StringerA) is different from Format(StringerB). However, I
wonder if there is any real reason why this is so because it appears
that the type comparison can be "isCompatible" instead of
"isIdentical" for the argument and return types, and things would
still work.
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/e5eb2946-e865-40cd-b1f7-3d445d423e00%40googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Henry

unread,
May 24, 2019, 11:38:42 PM5/24/19
to golang-nuts
Thanks for the reply.

Is there any specific use case that this intended behavior is supposed to solve? It appears to me that it is just a case of simplistic implementation where Go does not look deep enough to see if any dependent interfaces are identical. In this case, Go does not bother to look beyond Formatter interface in order to conclude whether FormatterA and FormatterB are identical. In here, I define 'identical' as being interchangeable, rather than semantic equality. So when I refer to TypeA and TypeB as identical, it means you can use TypeA in place of TypeB, and vice versa.

Back to the actual situation, Package A and Package B are developed in tandem by different teams. They have some communication on how their components will interact with each other and fit the bigger picture. They use interface to define contracts that are required in order for their components to work. Since Go interface is implicit, this is supposed to work fine. As an example, if I were to reduce the prior example as follows, it works fine. (Link to the playground: https://play.golang.org/p/zrpjSYTIyxZ)

import (
   
"fmt"
)

func main
() {

    str
:= MyString("Hello world!")
   
Print(str)
   
    strA
:=StringerA(str)
   
Print(strA)

}

type
StringerA interface {
   
String() string
}

type
StringerB interface {
   
String() string
}


type
MyString string

func
(s MyString) String() string {
   
return string(s)
}

func
Print(s StringerB) {
    fmt
.Println(s.String())
}

However, when I add a more complex interface, as in the first example involving Formatter, it breaks. Go fails to recognize that FormatterA and FormatterB are interchangeable despite the facts that their dependencies (StringerA and StringerB) are interchangeable. Visually, the components should look like this:

go.png

Let me know what you think.

Thanks.

Henry

Henry

unread,
May 24, 2019, 11:54:22 PM5/24/19
to golang-nuts
The posted diagram isn't correct. Updated the diagram with the new one.

go1.png

Dan Kortschak

unread,
May 24, 2019, 11:55:08 PM5/24/19
to golang-nuts
The interfaces that define the contracts should come from a third
package/source. The issue that I suspect you are hitting is that type
identity for interface types is based on the name, not the method set.
This means that, for example with your code below a function
PrintB(StringerB) and another function PrintA(StringerA) are not
assignable to the same variable unless it is type interface{}.

https://play.golang.org/p/a8aWwA6CAB_K // remove comment to see failure

roger peppe

unread,
May 25, 2019, 11:25:10 AM5/25/19
to Dan Kortschak, golang-nuts
There's a Go issue which is relevant here: https://github.com/golang/go/issues/8082


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages