Type And Interface Equivalence

92 views
Skip to first unread message

Kevin Conway

unread,
Sep 2, 2016, 9:51:52 AM9/2/16
to golan...@googlegroups.com
Not sure exactly how to describe this issue or the right words to use when talking about some of the concepts. Here's a best effort.

Given a type T that implements interfaces A1 and A2, T is an acceptable input for any function that requires an A1 or A2 as input. Given A1 and A2 are identical in definition, the return value of a function that returns and A1 is acceptable as input to a function at requires an A2, and vice versa.

However, a type T2 defined as a function that accepts an A1 cannot be satisfied by a function that accepts an A2. To illustrate, I've put together a playground that encounters this issue: https://play.golang.org/p/nsFCxP3T8H.

To give a slightly more concrete example of why this is a pain point, there is a common pattern among HTTP request routing libraries to define a context sensitive alternative to the http.Handler interface defined as:

    type HanderC interface {
        ServeHTTPC(context.Context, http.ResponseWriter, *http.Request)
    }

A benefit of the consistency across libraries is that my context sensitive handlers are portable to virtually any routing library I want without modification. However, each of these libraries also defines a form of middleware which wrap a HandlerC. The typical pattern for these libraries is to define a new type, Middleware, that is a function signature like func(HandlerC) HandlerC. A problem with this approach is that middleware become non-portable between libraries because a middleware defined as func(A.HandlerC) A.HandlerC cannot be used to satisfy func(B.HandlerC) B.HandlerC.

This constraint feels odd because the interfaces required in the function signatures are equivalent and, outside the context of that type definition, are treated as equivalent in all ways. It is additionally frustrating because the error message shown during compilation seems to indicate the names of the types are being used for validation rather than the interfaces themselves.

Is this an expected behaviour with rationale behind the implementation or is this considered a bug that will be resolved in future versions of go? Alternatively, what strategies exist for overcoming this issue when attempting to write code that is compatible with multiple, equivalent type definitions?

adon...@google.com

unread,
Sep 2, 2016, 11:12:14 AM9/2/16
to golang-nuts
On Friday, 2 September 2016 09:51:52 UTC-4, Kevin Conway wrote:
Given a type T that implements interfaces A1 and A2, T is an acceptable input for any function that requires an A1 or A2 as input. Given A1 and A2 are identical in definition, the return value of a function that returns an A1 is acceptable as input to a function at requires an A2, and vice versa.

However, a type T2 defined as a function that accepts an A1 cannot be satisfied by a function that accepts an A2. To illustrate, I've put together a playground that encounters this issue: https://play.golang.org/p/nsFCxP3T8H.

To give a slightly more concrete example of why this is a pain point, there is a common pattern among HTTP request routing libraries to define a context sensitive alternative to the http.Handler interface defined as:

    type HanderC interface {
        ServeHTTPC(context.Context, http.ResponseWriter, *http.Request)
    }

A benefit of the consistency across libraries is that my context sensitive handlers are portable to virtually any routing library I want without modification. However, each of these libraries also defines a form of middleware which wrap a HandlerC. The typical pattern for these libraries is to define a new type, Middleware, that is a function signature like func(HandlerC) HandlerC. A problem with this approach is that middleware become non-portable between libraries because a middleware defined as func(A.HandlerC) A.HandlerC cannot be used to satisfy func(B.HandlerC) B.HandlerC.

This constraint feels odd because the interfaces required in the function signatures are equivalent and, outside the context of that type definition, are treated as equivalent in all ways. It is additionally frustrating because the error message shown during compilation seems to indicate the names of the types are being used for validation rather than the interfaces themselves.

Is this an expected behaviour with rationale behind the implementation or is this considered a bug that will be resolved in future versions of go? Alternatively, what strategies exist for overcoming this issue when attempting to write code that is compatible with multiple, equivalent type definitions?

It's an expected consequence of the spec, so it's not a bug, but as you point out, it is certainly inconvenient at times and it is not a necessary restriction.  There has been some recent discussion of relaxing the rules for assignability so that if interface types A1 and A2 are mutually assignable, then compounds made from them, such as []A1 and []A2, or func(A1) and func(A2), would be mutually assignable as well.  See https://github.com/golang/go/issues/16209
Reply all
Reply to author
Forward
0 new messages